Snowflake in zes lessen

Van de architectuur tot het beheersen van je rekening. Virtual warehouses, data laden, micro-partities, Time Travel, zero-copy cloning en toegangsbeheer, met SQL die je direct kunt draaien.

6
Lessen
Beginner tot gevorderd
Niveau
6
Praktijkoefeningen
100%
Gratis

Les 1: Wat is Snowflake?

50 minuten Beginner Inclusief Demo

Snowflake Data Cloud: De Cloud-Native Data Warehouse

Snowflake is een volledig beheerde cloud data platform dat gebouwd is voor de cloud. Het combineert een data warehouse, data lake, en data sharing platform in één.

Key Insight

Traditionele data warehouses zijn duur en moeilijk te schalen. Snowflake's architectuur scheidt storage en compute, waardoor je onafhankelijk kunt schalen en alleen betaalt voor wat je gebruikt.

Snowflake's Multi-Cluster Architecture

Cloud Storage

Gecentraliseerde data opslag (S3, Azure Blob, GCS)

Virtual Warehouses

Elastische compute clusters voor query processing

Cloud Services

Beheer, security, metadata en query optimization

Data Sharing

Live data delen zonder kopiëren

Snowflake vs Traditionele Data Warehouses

Feature Snowflake Traditioneel Data Warehouse On-premise Warehouse
Architectuur Storage & Compute gescheiden Gekoppelde storage/compute Fysieke servers
Schaling ✅ Instant scaling (elastic) ❌ Manueel schalen ❌ Hardware upgrade nodig
Kosten Model Pay-per-use (secunde) Capaciteit/licentie gebaseerd CapEx + onderhoud
Maintenance ✅ Volledig beheerd ❌ Gedeeltelijk beheerd ❌ Volledig zelf
Data Sharing ✅ Live sharing (zero-copy) ❌ ETL/Export nodig ❌ Complexe integratie
Time Travel ✅ Tot 90 dagen historie ❌ Backup nodig ❌ Complex backup

Pro Tip: Wanneer Snowflake Kiezen?

Kies Snowflake wanneer je: (1) Onvoorspelbare workloads hebt, (2) Met meerdere cloud providers werkt, (3) Data wilt delen met externe partijen, (4) Geen database administrators wilt beheren, (5) Snelle time-to-value nodig hebt.

SnowSQL Voorbeeld: Eerste Verbinding met Snowflake

SQL - snowflake_connect.sql

-- Installeer SnowSQL: https://docs.snowflake.com/en/user-guide/snowsql-install-config

-- Verbind met Snowflake
!connect account=yz12345.eu-west-1
user=jansmit
password=************
warehouse=COMPUTE_WH
database=DEMO_DB
schema=PUBLIC
role=SYSADMIN;

-- Maak je eerste database
CREATE OR REPLACE DATABASE SALES_DB 
COMMENT = 'Productie database voor sales data';

-- Maak een nieuw schema
CREATE OR REPLACE SCHEMA SALES_DB.RAW 
COMMENT = 'Raw landing zone voor bron systemen';

-- Maak je eerste tabel
CREATE OR REPLACE TABLE SALES_DB.RAW.CUSTOMERS (
    CUSTOMER_ID NUMBER(38,0) AUTOINCREMENT START 1 INCREMENT 1,
    FIRST_NAME VARCHAR(100),
    LAST_NAME VARCHAR(100),
    EMAIL VARCHAR(255) UNIQUE,
    PHONE VARCHAR(20),
    ADDRESS VARCHAR(500),
    CITY VARCHAR(100),
    COUNTRY VARCHAR(100),
    CREATED_AT TIMESTAMP_NTZ(9) DEFAULT CURRENT_TIMESTAMP(),
    UPDATED_AT TIMESTAMP_NTZ(9) DEFAULT CURRENT_TIMESTAMP()
) 
COMMENT = 'Klant master data tabel';

-- Maak een stage voor data loading
CREATE OR REPLACE STAGE SALES_DB.RAW.CUSTOMER_STAGE
    URL = 's3://your-bucket/customer-data/'
    STORAGE_INTEGRATION = YOUR_S3_INTEGRATION
    FILE_FORMAT = (TYPE = 'CSV' SKIP_HEADER = 1);

-- Load data van S3
COPY INTO SALES_DB.RAW.CUSTOMERS
FROM @SALES_DB.RAW.CUSTOMER_STAGE
PATTERN = '.*customers.*\.csv'
ON_ERROR = 'CONTINUE';

-- Query data met Time Travel
SELECT * FROM SALES_DB.RAW.CUSTOMERS
AT(OFFSET => -60*5); -- Data van 5 minuten geleden

-- Toon alle databases
SHOW DATABASES;

-- Toon kosten van vandaag
SELECT 
    WAREHOUSE_NAME,
    SUM(CREDITS_USED) AS TOTAL_CREDITS,
    SUM(CREDITS_USED_COMPUTE) AS COMPUTE_CREDITS,
    SUM(CREDITS_USED_CLOUD_SERVICES) AS SERVICES_CREDITS
FROM INFORMATION_SCHEMA.WAREHOUSE_METERING_HISTORY
WHERE START_TIME >= CURRENT_DATE()
GROUP BY WAREHOUSE_NAME
ORDER BY TOTAL_CREDITS DESC;
        

Python Voorbeeld: Snowflake Connector met Snowpark

Python - snowpark_demo.py

# Installeer: pip install snowflake-connector-python snowflake-snowpark-python

from snowflake.snowpark import Session
from snowflake.snowpark.functions import col, sum as sum_, avg, count
import pandas as pd
import json

def create_snowflake_session():
    """Maak een Snowflake Snowpark sessie"""
    connection_params = {
        "account": "yz12345.eu-west-1",
        "user": "DATAPARTNER_USER",
        "password": "your_secure_password",
        "role": "DATA_ENGINEER",
        "warehouse": "COMPUTE_WH",
        "database": "ANALYTICS_DB",
        "schema": "PRODUCTION"
    }
    
    session = Session.builder.configs(connection_params).create()
    print("✅ Snowflake Snowpark sessie succesvol aangemaakt")
    return session

def demonstrate_snowpark_features(session):
    """Demonstreer Snowpark functionaliteiten"""
    
    # 1. Laad data in Snowpark DataFrame (lazy evaluation)
    sales_df = session.table("SALES_DB.RAW.SALES_TRANSACTIONS")
    
    print("Aantal rijen in sales:", sales_df.count())
    print("Schema van sales:", sales_df.schema)
    
    # 2. Data transformaties
    daily_sales = sales_df \
        .group_by("TRANSACTION_DATE") \
        .agg(
            sum_("AMOUNT").alias("TOTAL_SALES"),
            avg("AMOUNT").alias("AVG_TRANSACTION"),
            count("*").alias("TRANSACTION_COUNT")
        ) \
        .filter(col("TOTAL_SALES") > 10000) \
        .sort(col("TRANSACTION_DATE").desc())
    
    # 3. Toon SQL die gegenereerd wordt
    print("Generated SQL:")
    print(daily_sales.queries['queries'][0])
    
    # 4. Materialiseer de resultaten
    results = daily_sales.limit(10).collect()
    print("\nTop 10 verkoopdagen:")
    for row in results:
        print(f"{row.TRANSACTION_DATE}: €{row.TOTAL_SALES:,.2f}")
    
    # 5. Pandas integratie
    pandas_df = daily_sales.to_pandas()
    print(f"\nPandas DataFrame shape: {pandas_df.shape}")
    
    return pandas_df

def create_udf_for_calculations(session):
    """Maak een User Defined Function in Snowflake"""
    
    # Register een Python UDF
    @udf(name="calculate_discount", 
         input_types=[FloatType(), FloatType()], 
         return_type=FloatType(),
         is_permanent=True,
         stage_location="@PYTHON_UDFS",
         packages=["pandas"])
    def calculate_discount(price, discount_percentage):
        """Bereken de prijs na korting"""
        if discount_percentage > 50:
            discount_percentage = 50  # Max 50% korting
        return price * (1 - discount_percentage/100)
    
    print("✅ UDF succesvol geregistreerd in Snowflake")
    
    # Gebruik de UDF in een query
    result = session.sql("""
        SELECT 
            PRODUCT_ID,
            PRICE,
            DISCOUNT_PERCENTAGE,
            calculate_discount(PRICE, DISCOUNT_PERCENTAGE) as FINAL_PRICE
        FROM SALES_DB.RAW.PRODUCTS
        WHERE DISCOUNT_PERCENTAGE > 0
        LIMIT 10
    """).collect()
    
    return result

if __name__ == "__main__":
    # Maak Snowpark sessie
    session = create_snowflake_session()
    
    try:
        # Demo Snowpark features
        sales_data = demonstrate_snowpark_features(session)
        
        # Maak en gebruik UDF
        udf_results = create_udf_for_calculations(session)
        
        # Zero-copy cloning demo
        print("\n--- Zero-Copy Cloning Demo ---")
        session.sql("CREATE OR REPLACE DATABASE SALES_DB_CLONE CLONE SALES_DB").collect()
        print("Database succesvol gekloond zonder extra storage kosten!")
        
        # Time Travel demo
        print("\n--- Time Travel Demo ---")
        history = session.sql("""
            SELECT 
                MIN(CREATED) AS OLDEST_DATA,
                MAX(CREATED) AS NEWEST_DATA
            FROM SALES_DB.RAW.SALES_TRANSACTIONS
            AT(OFFSET => -3600) -- 1 uur geleden
        """).collect()
        print(f"Data van 1 uur geleden: {history[0]}")
        
    finally:
        session.close()
        print("\n✅ Sessie gesloten")
        

Oefening: Snowflake Architecture

Scenario: Je werkt voor een Nederlandse e-commerce bedrijf met:

  • 10TB aan historische order data in AWS S3
  • Real-time clickstream data via Kafka
  • Analytics team van 15 personen
  • Data science team die Python gebruikt
  • Noodzaak om data te delen met externe logistics partners

Opdracht: Ontwerp een Snowflake architectuur voor dit bedrijf:

  1. Hoeveel virtual warehouses heb je nodig?
  2. Welke size (XS, S, M, L, XL, XXL, XXXL)?
  3. Hoe organiseer je databases en schemas?
  4. Hoe implementeer je data sharing?
  5. Welke security features gebruik je?

Real-World Case: Bol.com

Uitdaging: Bol.com had data verspreid over meerdere on-premise data warehouses. Black Friday pieken zorgden voor performance issues.

Oplossing met Snowflake:

  • Geconsolideerde data in Snowflake Data Cloud
  • Auto-suspendeer warehouses tijdens daluren
  • Multi-cluster warehouses voor concurrente queries
  • Data Sharing met leveranciers voor real-time inventory
  • Snowpark voor Python/R machine learning

Resultaat: 60% kostenbesparing tov on-premise, 10x snellere queries tijdens piekmomenten, real-time data sharing met 200+ leveranciers.

Quick Knowledge Check

Vraag 1: Wat is het belangrijkste voordeel van Snowflake's architectuur?

A. Scheiding van storage en compute voor onafhankelijk schalen
B. Het werkt alleen op AWS
C. Het heeft geen SQL support nodig
D. Het is gratis voor kleine datasets

Vraag 2: Wat is Zero-Copy Cloning in Snowflake?

A. Het kopieert data naar een nieuwe locatie
B. Het maakt een instant clone zonder extra storage kosten
C. Het verwijdert oude data automatisch
D. Het comprimeert data zonder verlies

Vraag 3: Hoe lang kun je Time Travel gebruiken in Snowflake?

A. 7 dagen (standard)
B. Tot 90 dagen (afhankelijk van edition)
C. 30 dagen voor alle editions
D. Alleen 24 uur

Les 2: Virtual Warehouses & Scaling

50 minuten Beginner Met SQL-voorbeelden

Compute los van opslag

Een virtual warehouse is in Snowflake niets anders dan een cluster rekenkracht. Het bevat geen data: dat staat in de opslaglaag die alle warehouses delen. Zet je een warehouse uit, dan blijft je data gewoon staan en betaal je alleen nog opslag.

Dat is het punt waar Snowflake zich onderscheidt van een klassiek datawarehouse. Je financiële team kan een zware maandafsluiting draaien zonder dat de dashboards van de rest van het bedrijf trager worden, omdat beide groepen hun eigen warehouse gebruiken op dezelfde data.

Onthoud dit

Opslag reken je af per terabyte per maand. Compute reken je af per seconde dat een warehouse draait, met een minimum van 60 seconden per keer dat hij aangaat. Een warehouse die niets doet maar wel aanstaat, kost dus geld.

Maten en wat ze kosten

Elke stap omhoog verdubbelt het aantal servers én het verbruik per uur. Een Medium kost precies twee keer zoveel per uur als een Small — maar doet een zware query vaak ook in de helft van de tijd. Dan betaal je hetzelfde en ben je eerder klaar.

MaatCredits per uurWaarvoor
X-Small1Ontwikkelen, kleine transformaties, ad-hocvragen
Small2Dagelijkse laadprocessen op enkele miljoenen rijen
Medium4Dashboards met meerdere gebruikers tegelijk
Large8Zware joins, grote aggregaties, maandafsluiting
X-Large en groter16+Terabytes verwerken, verdubbelt per stap

Groter is niet altijd duurder

Duurt een query 40 minuten op een Small, probeer hem dan eens op een Medium. Loopt hij in 20 minuten, dan is je rekening identiek en heb je je antwoord twee keer zo snel. Schaalt hij niet mee, dan zit het knelpunt ergens anders — meestal in de query zelf, niet in de hoeveelheid rekenkracht.

Een warehouse aanmaken

SQL - warehouse_aanmaken.sql

-- Warehouse voor de dagelijkse laadprocessen
CREATE WAREHOUSE laden_wh WITH
  WAREHOUSE_SIZE      = 'SMALL'
  AUTO_SUSPEND        = 60      -- na 60 seconden niksdoen vanzelf uit
  AUTO_RESUME         = TRUE    -- gaat weer aan zodra er een query komt
  INITIALLY_SUSPENDED = TRUE;   -- start uit, niet aan

-- Warehouse voor de dashboards, met ruimte voor gelijktijdige gebruikers
CREATE WAREHOUSE bi_wh WITH
  WAREHOUSE_SIZE      = 'MEDIUM'
  AUTO_SUSPEND        = 300
  AUTO_RESUME         = TRUE
  MIN_CLUSTER_COUNT   = 1
  MAX_CLUSTER_COUNT   = 3       -- meer clusters zodra de wachtrij oploopt
  SCALING_POLICY      = 'STANDARD';

-- Maat aanpassen kan terwijl hij draait; lopende queries gaan gewoon door
ALTER WAREHOUSE laden_wh SET WAREHOUSE_SIZE = 'MEDIUM';

-- Handmatig uitzetten
ALTER WAREHOUSE laden_wh SUSPEND;
        

Twee manieren van schalen

Verwar deze twee niet, want ze lossen een ander probleem op.

Groter maken (van Small naar Medium) helpt tegen één zware query. Meer servers in hetzelfde cluster betekent meer geheugen en meer parallelle verwerking voor dat ene zware werkstuk.

Meer clusters (van 1 naar 3) helpt tegen veel gebruikers tegelijk. Zodra er queries in de wachtrij komen te staan, start Snowflake een extra cluster van dezelfde maat. Elke query wordt er niet sneller van, maar niemand staat meer te wachten.

Hoe je het herkent

Staan queries lang in de wachtrij maar lopen ze daarna snel? Dan heb je meer clusters nodig. Starten ze meteen maar duren ze lang? Dan is een grotere maat de juiste knop — of je query deugt niet.

Multi-cluster in de praktijk

SQL - schaalgedrag.sql

-- ECONOMY wacht langer voor hij een cluster bijzet: goedkoper,
-- maar gebruikers staan soms even in de rij.
ALTER WAREHOUSE bi_wh SET
  MAX_CLUSTER_COUNT = 4
  SCALING_POLICY    = 'ECONOMY';

-- Kijken of er wachtrijen ontstaan in het afgelopen etmaal
SELECT
    warehouse_name,
    SUM(avg_running)      AS gemiddeld_actief,
    SUM(avg_queued_load)  AS gemiddeld_in_wachtrij
FROM snowflake.account_usage.warehouse_load_history
WHERE start_time >= DATEADD(day, -1, CURRENT_TIMESTAMP())
GROUP BY warehouse_name
ORDER BY gemiddeld_in_wachtrij DESC;
        

De resultaatcache: gratis antwoorden

Draait iemand exact dezelfde query nog een keer en is de onderliggende data niet gewijzigd, dan geeft Snowflake het antwoord uit de cache. Dat kost nul credits en gaat vrijwel direct — het warehouse hoeft er niet eens voor aan te staan. De cache houdt een resultaat 24 uur vast, en bij hergebruik schuift die termijn op tot maximaal 31 dagen.

Dit is waarom een dashboard dat tien mensen achter elkaar openen maar één keer echt geld kost. Het is ook waarom je bij het meten van queryprestaties de cache moet uitzetten, anders meet je niets.

SQL - cache_testen.sql

-- Alleen voor het meten: zet de resultaatcache uit in deze sessie
ALTER SESSION SET USE_CACHED_RESULT = FALSE;

-- Terugzetten als je klaar bent met meten
ALTER SESSION SET USE_CACHED_RESULT = TRUE;

-- Welke queries kwamen uit de cache?
SELECT
    query_text,
    warehouse_name,
    execution_status,
    total_elapsed_time / 1000 AS seconden,
    credits_used_cloud_services
FROM snowflake.account_usage.query_history
WHERE start_time >= DATEADD(hour, -2, CURRENT_TIMESTAMP())
  AND warehouse_name IS NULL   -- geen warehouse gebruikt = uit de cache
ORDER BY start_time DESC
LIMIT 20;
        

Oefening: warehouses indelen

Situatie: een groothandel met drie soorten gebruik:

  • Elke nacht om 02:00 een laadproces van ongeveer 40 minuten op een Small
  • Overdag 25 mensen op Power BI-dashboards, vooral tussen 09:00 en 10:00
  • Twee analisten die onregelmatig zware ad-hocquery's draaien

Opdracht:

  1. Hoeveel warehouses maak je aan, en waarom niet één?
  2. Welke maat geef je elk, en welke zet je op multi-cluster?
  3. Welke AUTO_SUSPEND kies je per warehouse? Let op het verschil tussen een nachtelijk batchproces en een dashboard waar de hele dag door iemand op klikt.
  4. De analisten klagen dat hun query's traag zijn tijdens de ochtendpiek. Wat is er aan de hand als ze hun eigen warehouse hebben?

Richting: drie warehouses. Een Small voor het laden met AUTO_SUSPEND op 60. Een Medium voor BI met maximaal drie clusters en AUTO_SUSPEND rond 300, zodat hij tussen twee dashboardklikken niet steeds afslaat. Een X-Small voor de analisten die ze zelf tijdelijk opschalen. Zijn hun query's traag terwijl ze een eigen warehouse hebben, dan ligt het niet aan drukte van anderen: dan scant hun query te veel data. Dat is het onderwerp van les 4.

Les 3: Data laden in Snowflake

55 minuten Beginner Met SQL-voorbeelden

Stages: waar je bestanden landen

Voordat data in een tabel komt, staat hij op een stage. Dat is een plek waar Snowflake bestanden kan lezen. Er zijn er drie soorten, en het verschil zit in wie de opslag bezit.

SQL - stage_inrichten.sql

-- Vertel Snowflake hoe je bestanden eruitzien
CREATE OR REPLACE FILE FORMAT csv_formaat
  TYPE                         = 'CSV'
  FIELD_DELIMITER              = ';'
  SKIP_HEADER                  = 1
  FIELD_OPTIONALLY_ENCLOSED_BY = '"'
  NULL_IF                      = ('', 'NULL', 'null')
  EMPTY_FIELD_AS_NULL          = TRUE
  DATE_FORMAT                  = 'DD-MM-YYYY';

-- Externe stage op S3, met een storage integration voor de rechten
CREATE OR REPLACE STAGE orders_stage
  URL                 = 's3://mijn-bucket/orders/'
  STORAGE_INTEGRATION = s3_integratie
  FILE_FORMAT         = csv_formaat;

-- Kijken wat er klaarstaat zonder iets te laden
LIST @orders_stage;

-- En even in een bestand gluren
SELECT $1, $2, $3
FROM @orders_stage/orders_2026_01.csv
LIMIT 5;
        

COPY INTO: de werkpaard-opdracht

COPY INTO leest bestanden van een stage en schrijft ze naar een tabel. Snowflake onthoudt welke bestanden al geladen zijn, dus dezelfde opdracht twee keer draaien levert geen dubbele rijen op. Dat gedrag is standaard en bespaart je een hoop ellende.

SQL - copy_into.sql

CREATE OR REPLACE TABLE orders_bronze (
    order_id      NUMBER,
    klant_id      NUMBER,
    besteldatum   DATE,
    bedrag        NUMBER(12,2),
    status        VARCHAR,
    geladen_op    TIMESTAMP_NTZ DEFAULT CURRENT_TIMESTAMP()
);

-- Eerst droogzwemmen: valideren zonder te schrijven
COPY INTO orders_bronze
FROM @orders_stage
FILE_FORMAT = (FORMAT_NAME = csv_formaat)
VALIDATION_MODE = 'RETURN_ERRORS';

-- Daadwerkelijk laden
COPY INTO orders_bronze (order_id, klant_id, besteldatum, bedrag, status)
FROM (
    SELECT $1::NUMBER, $2::NUMBER, $3::DATE, $4::NUMBER(12,2), $5::VARCHAR
    FROM @orders_stage
)
FILE_FORMAT = (FORMAT_NAME = csv_formaat)
ON_ERROR     = 'CONTINUE';   -- foute rijen overslaan, de rest wel laden

-- Wat ging er mis bij de laatste lading?
SELECT * FROM TABLE(VALIDATE(orders_bronze, JOB_ID => '_last'));
        

Let op ON_ERROR

De standaard is ABORT_STATEMENT: één foute rij en de hele lading gaat niet door. Dat is vaak precies wat je wilt in productie, want half geladen data is erger dan geen data. Zet je het op CONTINUE, controleer dan altijd achteraf met VALIDATE wat je hebt overgeslagen — anders mis je stilletjes rijen.

Bestandsgrootte bepaalt je snelheid

Snowflake verdeelt het laden over de servers in je warehouse. Eén bestand van 10 GB wordt door één thread verwerkt; honderd bestanden van 100 MB gaan parallel. Mik op bestanden van 100 tot 250 MB gecomprimeerd. Veel kleinere bestandjes is ook niet goed: dan gaat de overhead per bestand overheersen.

Snowpipe: laden zodra het bestand er is

Met COPY INTO bepaal jij wanneer er geladen wordt. Met Snowpipe doet Snowflake dat zelf, binnen een minuut nadat er een bestand in je bucket verschijnt. Je betaalt per verwerkt bestand in plaats van per draaiend warehouse, wat gunstig is bij een gestage stroom kleine bestanden en juist ongunstig bij één grote lading per nacht.

SQL - snowpipe.sql

CREATE OR REPLACE PIPE orders_pipe
  AUTO_INGEST = TRUE
AS
COPY INTO orders_bronze (order_id, klant_id, besteldatum, bedrag, status)
FROM (
    SELECT $1::NUMBER, $2::NUMBER, $3::DATE, $4::NUMBER(12,2), $5::VARCHAR
    FROM @orders_stage
)
FILE_FORMAT = (FORMAT_NAME = csv_formaat);

-- Dit ARN zet je als melding op je S3-bucket
SHOW PIPES;

-- Draait hij, en loopt er iets achter?
SELECT SYSTEM$PIPE_STATUS('orders_pipe');

-- Wat heeft de pipe de afgelopen dag gedaan?
SELECT file_name, status, row_count, first_error_message, last_load_time
FROM TABLE(snowflake.information_schema.copy_history(
    TABLE_NAME      => 'ORDERS_BRONZE',
    START_TIME      => DATEADD(day, -1, CURRENT_TIMESTAMP())
))
ORDER BY last_load_time DESC;
        

Halfgestructureerde data: JSON zonder gedoe

Het VARIANT-datatype slaat JSON op zoals het is. Je hoeft vooraf geen kolommen te bedenken, en je bevraagt het met puntnotatie. Verandert de structuur van je bron, dan breekt je laadproces niet.

SQL - json_laden.sql

CREATE OR REPLACE TABLE events_raw (payload VARIANT);

COPY INTO events_raw
FROM @events_stage
FILE_FORMAT = (TYPE = 'JSON');

-- Velden eruit halen met puntnotatie; :: zet het om naar een echt type
SELECT
    payload:event_id::STRING            AS event_id,
    payload:gebruiker.naam::STRING      AS gebruikersnaam,
    payload:tijdstip::TIMESTAMP_NTZ     AS tijdstip,
    payload:bedrag::NUMBER(12,2)        AS bedrag
FROM events_raw
WHERE payload:type::STRING = 'bestelling';

-- Een array uitklappen naar losse rijen
SELECT
    e.payload:order_id::STRING AS order_id,
    r.value:sku::STRING        AS sku,
    r.value:aantal::NUMBER     AS aantal
FROM events_raw e,
     LATERAL FLATTEN(input => e.payload:regels) r;
        

Oefening: een laadstraat kiezen

Situatie: je krijgt drie bronnen binnen:

  • Elke nacht één CSV-export van 8 GB uit het ERP-systeem
  • Doorlopend JSON-bestandjes van ongeveer 2 MB uit een webshop, een paar per minuut
  • Eens per kwartaal een handmatige Excel-aanlevering van de afdeling inkoop

Opdracht:

  1. Kies per bron tussen COPY INTO en Snowpipe, en onderbouw waarom.
  2. Wat doe je met die 8 GB voordat je hem laadt?
  3. Welk datatype gebruik je voor de webshopdata, en waarom niet meteen losse kolommen?
  4. Welke ON_ERROR-instelling kies je voor het ERP-bestand, en welke voor de handmatige aanlevering?

Richting: het ERP-bestand splits je in stukken van 100 tot 250 MB en laad je met COPY INTO op een vast tijdstip; ON_ERROR = 'ABORT_STATEMENT', want een halve financiële lading wil je niet. De webshopdata is precies waar Snowpipe voor bedoeld is: klein, continu, en als VARIANT opgeslagen zodat een extra veld in de JSON niets breekt. De kwartaalaanlevering doe je met de hand, met VALIDATION_MODE vooraf — bij handwerk zijn fouten de regel, niet de uitzondering.

Les 4: Micro-partities en performance

50 minuten Gevorderd Met SQL-voorbeelden

Wat Snowflake onder water doet

Snowflake bewaart je tabellen in micro-partities: blokken van 50 tot 500 MB onverpakte data, kolomsgewijs opgeslagen. Je maakt ze niet zelf aan en je kunt ze niet aanpassen; Snowflake deelt je data automatisch op zodra je laadt.

Van elke micro-partitie houdt Snowflake bij wat de kleinste en grootste waarde per kolom is. Dat is het hele geheim achter de snelheid. Vraag je om bestellingen van januari 2026, dan kijkt Snowflake eerst in die metadata en slaat elke partitie over waarin geen enkele datum in die maand voorkomt. Dat heet pruning.

Onthoud dit

Een query wordt niet traag omdat je tabel groot is. Hij wordt traag omdat Snowflake te veel partities moet openen. Alle optimalisatie in Snowflake draait om één vraag: hoe zorg ik dat er minder gescand hoeft te worden?

Meten hoeveel je scant

Het queryprofiel vertelt je precies hoeveel partities er zijn gelezen van het totaal. Staat daar "3.200 van 3.200", dan is er niets weggeprund en scan je je hele tabel.

SQL - pruning_meten.sql

-- Hoeveel partities raakten je zwaarste query's aan?
SELECT
    query_id,
    LEFT(query_text, 80)                          AS query,
    partitions_scanned,
    partitions_total,
    ROUND(100 * partitions_scanned / NULLIF(partitions_total, 0), 1) AS pct_gescand,
    bytes_spilled_to_local_storage                AS gemorst_lokaal,
    total_elapsed_time / 1000                     AS seconden
FROM snowflake.account_usage.query_history
WHERE start_time >= DATEADD(day, -7, CURRENT_TIMESTAMP())
  AND partitions_total > 0
ORDER BY partitions_scanned DESC
LIMIT 20;
        

De kolom gemorst_lokaal verdient aandacht. Morst een query naar schijf, dan paste de bewerking niet in het geheugen van je warehouse. Dat is het duidelijkste signaal dat een maat groter wél helpt — in tegenstelling tot de meeste andere gevallen.

Clustering: de volgorde van je data

Bij het laden belandt data in de volgorde waarin hij binnenkomt. Laad je elke nacht de bestellingen van die dag, dan staan ze vanzelf netjes op datum en prunet een datumfilter uitstekend. Filter je echter vooral op klant, dan staan die klantrijen verspreid over duizenden partities en helpt pruning je niets.

Een clustering key laat Snowflake je data op de achtergrond herordenen op de kolommen waarop je filtert.

SQL - clustering.sql

-- Eerst kijken of het nodig is. Let op average_overlaps:
-- hoe hoger, hoe meer partities elkaar overlappen en hoe slechter het pruned.
SELECT SYSTEM$CLUSTERING_INFORMATION('orders', '(besteldatum, klant_id)');

-- Pas daarna instellen
ALTER TABLE orders CLUSTER BY (besteldatum, klant_id);

-- Wat kost het herordenen?
SELECT
    table_name,
    SUM(credits_used) AS credits,
    SUM(num_rows_reclustered) AS rijen_herordend
FROM snowflake.account_usage.automatic_clustering_history
WHERE start_time >= DATEADD(day, -30, CURRENT_TIMESTAMP())
GROUP BY table_name
ORDER BY credits DESC;

-- Weer uitzetten als het meer kost dan het oplevert
ALTER TABLE orders DROP CLUSTERING KEY;
        

Clustering kost doorlopend geld

Snowflake herordent op de achtergrond, en dat verbruikt credits zolang je blijft laden. Zet het alleen aan op tabellen boven ongeveer een terabyte die je vaak bevraagt op een kolom waarop ze niet vanzelf geordend zijn. Op een tabel van tien miljoen rijen is het weggegooid geld. Zet de kolom met de minste verschillende waarden vooraan in de key.

Vier dingen die echt schelen

Selecteer geen kolommen die je niet gebruikt. Snowflake slaat kolomsgewijs op, dus SELECT * op een tabel met tachtig kolommen leest alle tachtig. Noem de vijf die je nodig hebt en je leest een fractie.

Filter op de kolom zelf, niet op een bewerking ervan. Zodra je een functie om een kolom heen zet, kan Snowflake de metadata niet meer gebruiken en vervalt het pruning.

Gebruik geen DISTINCT om dubbele rijen van een slechte join te verbergen. Dat verdoezelt het probleem en kost een volledige sortering.

Materialiseer wat je steeds opnieuw berekent. Een zware aggregatie die tien dashboards voeden moet, hoort één keer berekend te worden.

SQL - pruning_slopen.sql

-- FOUT: de functie om besteldatum heen maakt pruning onmogelijk
SELECT klant_id, SUM(bedrag)
FROM orders
WHERE YEAR(besteldatum) = 2026
GROUP BY klant_id;

-- GOED: een bereik op de kolom zelf, metadata blijft bruikbaar
SELECT klant_id, SUM(bedrag)
FROM orders
WHERE besteldatum >= '2026-01-01'
  AND besteldatum <  '2027-01-01'
GROUP BY klant_id;

-- FOUT: hoofdletterongevoelig zoeken sloopt het pruning ook
SELECT * FROM klanten WHERE UPPER(email) = 'INFO@VOORBEELD.NL';

-- GOED: sla de genormaliseerde waarde op bij het laden en filter daarop
SELECT * FROM klanten WHERE email_lower = 'info@voorbeeld.nl';
        

Materialized views

Een materialized view bewaart het resultaat en houdt het automatisch bij als de brontabel verandert. Dat kost opslag en achtergrondcredits, maar bespaart rekenwerk bij elke bevraging. Ze kennen flinke beperkingen: geen joins, geen window-functies, één tabel.

Heb je wél een join of een window-functie nodig, kijk dan naar dynamic tables. Die zijn bedoeld voor hele transformatieketens met een doorlooptijd die je zelf bepaalt. Het verschil tussen beide staat uitgewerkt in het artikel over Dynamic Tables versus views, onderaan deze pagina.

SQL - materialized_view.sql

CREATE OR REPLACE MATERIALIZED VIEW omzet_per_dag AS
SELECT
    besteldatum,
    COUNT(*)      AS aantal_orders,
    SUM(bedrag)   AS omzet
FROM orders
GROUP BY besteldatum;

-- Wat kost het onderhoud ervan?
SELECT table_name, SUM(credits_used) AS credits
FROM snowflake.account_usage.materialized_view_refresh_history
WHERE start_time >= DATEADD(day, -30, CURRENT_TIMESTAMP())
GROUP BY table_name
ORDER BY credits DESC;
        

Oefening: een trage query ontleden

Situatie: een rapportage over klantgedrag draait elke ochtend en duurt veertig minuten op een Medium. De tabel orders telt 2 miljard rijen en wordt dagelijks bijgeladen. Uit het queryprofiel blijkt: 11.800 van 12.000 partities gescand, 40 GB gemorst naar lokale schijf.

Opdracht:

  1. Wat vertelt dat aantal gescande partities je over de filters in de query?
  2. Wat betekent het morsen naar schijf, en welke knop helpt daar wél tegen?
  3. De query filtert op WHERE DATE_TRUNC('month', besteldatum) = '2026-01-01'. Wat is daar mis mee en hoe schrijf je het anders?
  4. Zou je hier clustering aanzetten? Waarop let je bij die afweging?

Richting: bijna alles scannen betekent dat er effectief niet gefilterd wordt — en die DATE_TRUNC om de kolom heen is precies de reden. Herschrijf naar een bereik met >= en < en het pruning komt terug. Het morsen zegt dat de bewerking niet in het geheugen past; dat is het geval waarin een stap groter echt helpt. Clustering is hier te overwegen vanwege de omvang, maar probeer eerst de herschreven query: als de data al op laaddatum geordend staat, prunet een datumbereik vanzelf goed en bespaar je de doorlopende clusteringkosten.

Les 5: Time Travel en Zero-Copy Cloning

45 minuten Gemiddeld Met SQL-voorbeelden

Terugkijken in de tijd

Omdat Snowflake micro-partities nooit aanpast maar vervangt, staat de oude versie er nog. Time Travel maakt die bereikbaar. Je kunt een tabel bevragen zoals hij er een uur geleden uitzag, of vlak voor een specifieke query.

Op Standard is de bewaartermijn één dag. Op Enterprise kun je per tabel tot negentig dagen instellen. Die termijn kost opslag, dus zet hem niet overal maximaal.

SQL - time_travel.sql

-- Drie manieren om terug te kijken
SELECT * FROM orders AT(OFFSET => -60 * 15);          -- 15 minuten geleden
SELECT * FROM orders AT(TIMESTAMP => '2026-10-24 08:00:00'::TIMESTAMP_NTZ);
SELECT * FROM orders BEFORE(STATEMENT => '01b2c3d4-...');  -- vlak voor die query

-- Wat is er precies veranderd sinds vanochtend?
SELECT COUNT(*) AS nu FROM orders;
SELECT COUNT(*) AS vanochtend FROM orders AT(TIMESTAMP => '2026-10-24 06:00:00'::TIMESTAMP_NTZ);

-- Per tabel instellen hoe ver je terug wilt kunnen
ALTER TABLE orders SET DATA_RETENTION_TIME_IN_DAYS = 30;

-- Een verwijderde tabel terughalen
UNDROP TABLE orders;
        

Na een foute UPDATE

Heeft iemand een UPDATE zonder WHERE gedraaid, zoek dan het query-id op in query_history en herstel met BEFORE(STATEMENT => ...). Dat is nauwkeuriger dan een tijdstip gokken. Doe het wel snel: zodra de bewaartermijn verstrijkt, is de oude versie echt weg.

Herstellen in de praktijk

SQL - herstellen.sql

-- 1. Zoek de schuldige query op
SELECT query_id, query_text, user_name, start_time, rows_updated
FROM snowflake.account_usage.query_history
WHERE query_type = 'UPDATE'
  AND start_time >= DATEADD(hour, -4, CURRENT_TIMESTAMP())
ORDER BY start_time DESC;

-- 2. Controleer eerst of de oude stand er goed uitziet
SELECT * FROM orders BEFORE(STATEMENT => '01b2c3d4-0000-abcd-0000-000000000001')
LIMIT 20;

-- 3. Zet een kopie van de oude stand apart
CREATE TABLE orders_herstel AS
SELECT * FROM orders BEFORE(STATEMENT => '01b2c3d4-0000-abcd-0000-000000000001');

-- 4. Pas daarna omwisselen; SWAP is atomair
ALTER TABLE orders SWAP WITH orders_herstel;
        

Zero-copy cloning

Een clone is een nieuwe tabel, schema of database die in eerste instantie naar dezelfde micro-partities wijst. Er wordt niets gekopieerd, dus het gaat vrijwel direct en kost in het begin geen extra opslag. Pas als je in de clone gaat schrijven, ontstaan er nieuwe partities en betaal je voor dat verschil.

Dit is waarom je in Snowflake zonder bezwaar een volledige acceptatieomgeving kunt neerzetten op productiedata, of een experiment kunt doen zonder iemand in de weg te zitten.

SQL - cloning.sql

-- Hele database klonen voor acceptatie
CREATE DATABASE acc_db CLONE prod_db;

-- Klonen zoals het er gisteren uitzag: Time Travel en cloning combineren
CREATE TABLE orders_gisteren
CLONE orders AT(TIMESTAMP => DATEADD(day, -1, CURRENT_TIMESTAMP()));

-- Veilig experimenteren met een zware wijziging
CREATE TABLE orders_test CLONE orders;
ALTER TABLE orders_test CLUSTER BY (klant_id);
-- meten, vergelijken, en daarna opruimen
DROP TABLE orders_test;
        

Twee dingen om te weten

Een clone neemt geen toegangsrechten op de onderliggende objecten over; die ken je opnieuw toe. En hoewel klonen zelf gratis is, kost een clone van een tabel die je daarna volledig herschrijft wél de volle opslag. Ruim testclones dus op.

Oefening: herstel na een ongeluk

Situatie: om 14:20 heeft een collega een DELETE FROM klanten gedraaid zonder WHERE. Het is nu 15:05. De tabel staat op de standaardbewaartermijn van één dag en er zijn sindsdien alweer nieuwe klanten aangemaakt door de applicatie.

Opdracht:

  1. Hoe vind je het exacte moment terug in plaats van 14:20 te gokken?
  2. Waarom zet je de oude stand eerst in een aparte tabel in plaats van meteen terug te schrijven?
  3. Hoe zorg je dat de klanten die ná 14:20 zijn aangemaakt niet verdwijnen bij het herstel?
  4. Wat stel je achteraf in zodat dit minder pijn doet als het nog eens gebeurt?

Richting: zoek het query-id in query_history en gebruik BEFORE(STATEMENT => ...). Zet die stand in klanten_herstel zodat je kunt controleren voordat je iets omwisselt. Voor de nieuwe klanten combineer je: neem de herstelde rijen en voeg daar met MERGE de rijen uit de huidige tabel aan toe die na het incident zijn aangemaakt. En verhoog daarna DATA_RETENTION_TIME_IN_DAYS op je kerntabellen — één dag is krap als een fout pas de volgende ochtend opvalt.

Les 6: Toegangsbeheer en kosten

50 minuten Gemiddeld Met SQL-voorbeelden

Rechten hangen aan rollen, niet aan mensen

In Snowflake krijgt een gebruiker nooit rechtstreeks rechten op een tabel. Rechten horen bij een rol, en die rol ken je toe aan een gebruiker. Rollen kunnen elkaar bovendien bevatten, zodat je een hiërarchie bouwt.

Die laatste eigenschap wordt vaak verkeerd begrepen. Geef je rol A aan rol B, dan krijgt B álles wat A mag. Je bouwt dus van beperkt naar ruim: de analistenrol gaat in de engineersrol, niet andersom.

SQL - rollen.sql

USE ROLE SECURITYADMIN;

CREATE ROLE analist;
CREATE ROLE engineer;

-- Analisten mogen lezen
GRANT USAGE   ON DATABASE prod_db            TO ROLE analist;
GRANT USAGE   ON SCHEMA   prod_db.gold       TO ROLE analist;
GRANT SELECT  ON ALL TABLES IN SCHEMA prod_db.gold TO ROLE analist;
-- ook op tabellen die er later bij komen
GRANT SELECT  ON FUTURE TABLES IN SCHEMA prod_db.gold TO ROLE analist;
GRANT USAGE   ON WAREHOUSE bi_wh             TO ROLE analist;

-- Engineers mogen alles wat analisten mogen, plus schrijven
GRANT ROLE analist TO ROLE engineer;
GRANT ALL ON SCHEMA prod_db.silver TO ROLE engineer;
GRANT USAGE ON WAREHOUSE laden_wh  TO ROLE engineer;

-- Pas daarna aan mensen toekennen
GRANT ROLE analist TO USER jansen;

-- Controleren wat een rol werkelijk mag
SHOW GRANTS TO ROLE analist;
        

Vergeet FUTURE GRANTS niet

Zonder ON FUTURE TABLES ziet je analistenrol elke tabel die je morgen aanmaakt niet. Dat is de meest voorkomende klacht na een nieuwe inrichting: "ik zie de tabel wel in de lijst maar krijg geen rechten". En USAGE op database én schema is altijd nodig voordat SELECT op een tabel iets doet.

Niet iedereen mag alles zien

Twee mechanismen beperken wat een rol binnen een tabel te zien krijgt. Een masking policy verbergt de inhoud van een kolom; een row access policy beperkt welke rijen zichtbaar zijn.

SQL - maskeren.sql

-- E-mailadressen alleen zichtbaar voor wie dat nodig heeft
CREATE OR REPLACE MASKING POLICY email_masker AS (val STRING) RETURNS STRING ->
  CASE
    WHEN CURRENT_ROLE() IN ('ENGINEER', 'ACCOUNTADMIN') THEN val
    ELSE REGEXP_REPLACE(val, '.+@', '*****@')
  END;

ALTER TABLE klanten MODIFY COLUMN email
  SET MASKING POLICY email_masker;

-- Accountmanagers zien alleen hun eigen regio
CREATE OR REPLACE ROW ACCESS POLICY regio_filter AS (regio STRING) RETURNS BOOLEAN ->
  CURRENT_ROLE() = 'ENGINEER'
  OR regio = CURRENT_USER();   -- in de praktijk via een koppeltabel

ALTER TABLE klanten ADD ROW ACCESS POLICY regio_filter ON (regio);
        

Grip op je rekening

Snowflake rekent af in credits. Je grootste kostenpost is bijna altijd compute, en de meestvoorkomende verspilling is een warehouse dat aanstaat zonder dat er iemand op werkt. AUTO_SUSPEND uit les 2 is daarom je belangrijkste knop.

Daarnaast zet je resource monitors in. Die waarschuwen je bij een percentage van je budget en kunnen een warehouse desnoods stilleggen.

SQL - kosten_bewaken.sql

USE ROLE ACCOUNTADMIN;

CREATE OR REPLACE RESOURCE MONITOR maandbudget WITH
  CREDIT_QUOTA = 500
  FREQUENCY    = MONTHLY
  START_TIMESTAMP = IMMEDIATELY
  TRIGGERS
    ON 75  PERCENT DO NOTIFY
    ON 90  PERCENT DO NOTIFY
    ON 100 PERCENT DO SUSPEND            -- lopende query's mogen afmaken
    ON 110 PERCENT DO SUSPEND_IMMEDIATE; -- hard stoppen

ALTER WAREHOUSE bi_wh SET RESOURCE_MONITOR = maandbudget;

-- Waar gaan de credits heen?
SELECT
    warehouse_name,
    ROUND(SUM(credits_used), 1) AS credits,
    ROUND(SUM(credits_used) * 2.50, 0) AS indicatie_euro  -- pas aan naar je eigen tarief
FROM snowflake.account_usage.warehouse_metering_history
WHERE start_time >= DATEADD(day, -30, CURRENT_TIMESTAMP())
GROUP BY warehouse_name
ORDER BY credits DESC;

-- Welke gebruikers verbruiken het meest?
SELECT
    user_name,
    COUNT(*)                                   AS aantal_queries,
    ROUND(SUM(total_elapsed_time) / 3600000, 1) AS draaiuren
FROM snowflake.account_usage.query_history
WHERE start_time >= DATEADD(day, -30, CURRENT_TIMESTAMP())
GROUP BY user_name
ORDER BY draaiuren DESC
LIMIT 15;
        

Waar je het eerst naar kijkt

Staat AUTO_SUSPEND overal aan en op een verstandige waarde? Draait er een warehouse groter dan nodig? Scant een terugkerende query bijna de hele tabel? Die drie vragen verklaren in de praktijk het leeuwendeel van een rekening die hoger uitvalt dan verwacht.

Oefening: inrichting voor een nieuw team

Situatie: er komt een team van drie externe analisten bij. Ze moeten rapportages kunnen maken op de gold-laag, maar mogen geen e-mailadressen of geboortedata van klanten zien, en niets kunnen wijzigen. Je verwacht dat ze vooral 's ochtends werken.

Opdracht:

  1. Welke rol maak je, en welke rechten geef je precies? Vergeet de twee niveaus USAGE niet.
  2. Hoe zorg je dat ze ook de tabellen zien die volgende maand worden toegevoegd?
  3. Welk mechanisme gebruik je voor de afgeschermde kolommen, en waarom niet gewoon een aparte view?
  4. Geef je ze een eigen warehouse of laat je ze meedraaien op het bestaande BI-warehouse? Welke overweging geeft de doorslag?
  5. Welke resource monitor hang je eraan?

Richting: een rol analist_extern met USAGE op database en schema, SELECT op alle huidige én toekomstige tabellen in gold, en USAGE op een warehouse. Masking policies verdienen de voorkeur boven een aparte view, omdat de afscherming dan aan de kolom hangt en niet te omzeilen is door de onderliggende tabel te bevragen. Een eigen X-Small warehouse is verdedigbaar omdat je dan precies ziet wat dit team kost en het de bestaande dashboards niet raakt tijdens de ochtendpiek — hang er dan wel een resource monitor aan met een bescheiden quotum en een SUSPEND op 100 procent.

Snowflake Carrière Kansen in Nederland

De vraag naar Snowflake experts groeit met 300% per jaar in Nederland. Gemiddelde salarissen (2024):

€50K - €65K
Junior Snowflake Engineer
€65K - €90K
Snowflake Data Engineer
€80K - €110K+
Senior Snowflake Architect
€95K - €130K+
Lead Cloud Data Engineer

Tip: Snowflake certificeringen verhogen je salaris met 20-30%. De SnowPro Core certificering is de beste start.

Snowflake Certificering Pad

Na het voltooien van deze cursus ben je klaar voor:

SnowPro Core

Fundamentele kennis van Snowflake Data Cloud

SnowPro Advanced: Architect

Geavanceerde architectuur en implementatie

SnowPro Advanced: Data Engineer

Data engineering en pipelines op Snowflake

SnowPro Advanced: Administrator

Security, governance en beheer

Snowflake Learning Resources

Snowflake als dataplatform

Hoe Snowflake in een moderne data-architectuur past: opslag, compute, kosten en beheer.

Lees de platformpagina

Wat is Snowflake?

De basis uitgelegd: architectuur, virtual warehouses, micro-partities en waar het zich onderscheidt.

Lees het artikel

Dynamic Tables vs Views

Verschil in verversing, kosten, latency en onderhoud, met SQL-voorbeelden en een beslisboom.

Lees het artikel

Query Optimizer

Plak een trage query en krijg concrete suggesties voor clustering, pruning en warehouse-grootte.

Open de tool

Jouw Snowflake Learning Journey

Na deze complete guide kun je door naar:

Actuele Snowflake Vacatures

Bekijk onze Snowflake vacatures voor de nieuwste kansen bij top bedrijven in Nederland zoals ING, ABN AMRO, KLM, en Philips.

Snowflake vs Andere Cloud Data Platforms

Platform Sterke Punten Zwakke Punten Best Voor Prijzen (Nederland)
Snowflake Zero-copy cloning, Data Sharing, Multi-cloud Kostbaar voor kleine workloads Enterprise, Data Sharing, Multi-cloud €2-€4 per credit
Databricks Machine Learning, Delta Lake, Spark Complexer voor pure SQL Data Science, ML, Streaming €0.40-€0.70 per DBU
Google BigQuery Serverless, Machine Learning, Google Cloud Vendor lock-in, minder multi-cloud Google Cloud ecosystem €4.80 per TB (on-demand)
Azure Synapse Azure integratie, T-SQL compatible Minder mature data sharing Microsoft ecosystem €1.20 per hour (DWU100)
Amazon Redshift AWS integratie, goed voor grote datasets Minder flexibel schalen AWS native workloads €0.85 per hour (dc2.large)

Let op: prijzen zijn indicatief en kunnen variëren per regio en gebruik.