Ebook · Hoofdstuk 5 van 10

Data Modelling Technieken

Star schema, fact types, SCD's en de geavanceerde patronen die je in elke serieuze warehouse-build tegenkomt.

Het hart van het warehouse

24 min leestijd Gevorderd

Modellering is de plek waar slechte beslissingen jaren doorwerken. Een goede sterrenstructuur is leesbaar voor BI-developers, snel voor de query-engine en flexibel genoeg om mee te groeien met de business. Dit hoofdstuk gaat dieper dan de basis: niet alleen wat een fact en dimensie is, maar welke varianten er zijn en wanneer je ze inzet.

Star schema versus snowflake

In een star schema is elke dimensie één gedenormaliseerde tabel. In een snowflake schema worden dimensies verder genormaliseerd in subtabellen. Het verschil:

-- Star: één dim_product
CREATE TABLE dim_product (
  product_key      INT PRIMARY KEY,
  product_id       VARCHAR(50),
  product_name     VARCHAR(200),
  category         VARCHAR(100),
  subcategory      VARCHAR(100),
  brand            VARCHAR(100),
  brand_country    VARCHAR(50)
);

-- Snowflake: dim_product genormaliseerd in subtabellen
CREATE TABLE dim_product (
  product_key      INT PRIMARY KEY,
  product_id       VARCHAR(50),
  product_name     VARCHAR(200),
  brand_key        INT,
  category_key     INT
);
CREATE TABLE dim_brand (
  brand_key        INT PRIMARY KEY,
  brand_name       VARCHAR(100),
  country_key      INT
);
CREATE TABLE dim_category (
  category_key     INT PRIMARY KEY,
  category_name    VARCHAR(100),
  subcategory_name VARCHAR(100)
);

De praktische regel: kies star, tenzij je een hele goede reden hebt om te snowflaken. Star is sneller voor query engines (minder joins), simpeler voor BI-tools en intuïtiever voor gebruikers. Snowflake bespaart marginaal opslag op enorme dimensies — maar in cloud-warehouses met columnstore-compressie is dat geen issue meer.

Wanneer is snowflake wel verdedigbaar?

Drie scenario's: (1) een dimensie heeft attributen die afzonderlijk een eigen analytische rol spelen — een productcategorie die ook gebruikers- of regelgevings-attributen heeft. (2) Outrigger-dimensies in SCD Type 5: snelwisselende attributen los van langzaam wisselende. (3) Een Inmon-stijl 3NF-laag onder een Kimball-goldlaag (zie hoofdstuk 2). Buiten deze gevallen creëer je vooral extra joins zonder waarde.

Fact tables: drie hoofdtypen

Niet elke fact-tabel is hetzelfde. Kimball onderscheidt drie typen, elk met eigen toepassingen:

1. Transaction fact

Eén rij per gebeurtenis (verkoop, klik, login). Meest voorkomende type. Granular en flexibel — je kunt altijd nog aggregeren, maar nooit detail terughalen dat je niet hebt opgeslagen.

CREATE TABLE fact_sales (
  sales_key       BIGINT IDENTITY,
  date_key        INT,
  customer_key    INT,
  product_key     INT,
  store_key       INT,
  order_id        VARCHAR(50),    -- degenerate dimension
  quantity        INT,
  unit_price      DECIMAL(10,2),
  discount        DECIMAL(10,2),
  net_amount      DECIMAL(12,2),
  load_ts         TIMESTAMP
);

2. Periodic snapshot fact

Eén rij per entiteit per periode (dagelijks saldo, maandelijkse voorraad). Geweldig voor trends en KPI's, maar grof.

CREATE TABLE fact_inventory_daily (
  date_key        INT,
  product_key     INT,
  store_key       INT,
  units_on_hand   INT,
  units_sold      INT,
  units_received  INT,
  inventory_value DECIMAL(12,2),
  PRIMARY KEY (date_key, product_key, store_key)
);

3. Accumulating snapshot fact

Eén rij per business proces dat door fasen gaat (een order van geplaatst naar geleverd). Datums voor elke mijlpaal — rij wordt geüpdatet als er fasen voorbij gaan.

CREATE TABLE fact_order_lifecycle (
  order_id          VARCHAR(50) PRIMARY KEY,
  customer_key      INT,
  date_placed_key   INT,
  date_paid_key     INT,
  date_shipped_key  INT,
  date_delivered_key INT,
  hours_to_pay       INT,
  hours_to_ship      INT,
  hours_to_deliver   INT,
  total_amount       DECIMAL(12,2)
);

Elke fase update dezelfde rij. Maakt cycle-time-analyses (gemiddeld 36 uur tot levering?) triviaal.

Additieve, semi-additieve en niet-additieve metingen

Niet elke meting in een fact-tabel kan op elke dimensie gesommeerd worden. Drie categorieën:

Documenteer dit per fact-kolom. BI-developers die niet-additieve metingen optellen leveren rapportages die er goed uitzien én structureel fout zijn.

Slowly Changing Dimensions: 0 t/m 7

Klantgegevens veranderen. Hoe ga je daarmee om? Kimball benoemt zeven SCD-types:

TypeStrategieWanneer
0Nooit wijzigen — bevries originele waardeOnveranderlijke attributen (geboortedatum)
1Overschrijven — geen historieCorrecties van fouten
2Nieuwe rij per wijziging — volledige historieStandaard voor business-relevante wijzigingen
3Extra kolommen voor "previous" waardeBeperkte historie (alleen vorige stand)
4Mini-dimensie met snelwisselende attributenKlanten met regelmatig wisselende segmenten
5Combinatie van 1 en 4 met outriggerBeperkte type-2-overhead nodig
6Combinatie van 1, 2 en 3Zowel huidige als historische analyses
7Dual surrogate keys: huidig + historisch"As-is" én "as-was" rapporteren

In de praktijk gebruik je 95% van de tijd Type 1 of Type 2. De overige varianten zijn voor specifieke edge cases.

SCD Type 2 in detail

De volledige implementatie met effective dating:

CREATE TABLE dim_customer (
  customer_key     INT IDENTITY PRIMARY KEY,
  customer_id      VARCHAR(50),
  name             VARCHAR(200),
  email            VARCHAR(200),
  segment          VARCHAR(50),
  country          VARCHAR(50),
  valid_from       DATE,
  valid_to         DATE,
  is_current       BOOLEAN,
  hash_diff        CHAR(64)        -- voor change-detection
);

-- Trick: gebruik een hash om te detecteren of er iets is veranderd
-- in plaats van per kolom te vergelijken
WITH src AS (
  SELECT customer_id, name, email, segment, country,
         SHA2(CONCAT_WS('|', name, email, segment, country), 256) AS hash_diff
  FROM stg_customers
),
to_change AS (
  SELECT s.*
  FROM   src s
  JOIN   dim_customer d ON d.customer_id = s.customer_id AND d.is_current
  WHERE  d.hash_diff <> s.hash_diff
)
-- 1. Verlopen sluiten
UPDATE dim_customer
   SET valid_to = CURRENT_DATE - 1, is_current = FALSE
 WHERE customer_id IN (SELECT customer_id FROM to_change)
   AND is_current = TRUE;

-- 2. Nieuwe rijen
INSERT INTO dim_customer (customer_id, name, email, segment, country,
                          valid_from, valid_to, is_current, hash_diff)
SELECT customer_id, name, email, segment, country,
       CURRENT_DATE, '9999-12-31', TRUE, hash_diff
FROM   to_change

UNION ALL

-- 3. Volledig nieuwe klanten
SELECT s.customer_id, s.name, s.email, s.segment, s.country,
       CURRENT_DATE, '9999-12-31', TRUE, s.hash_diff
FROM   src s
LEFT   JOIN dim_customer d ON d.customer_id = s.customer_id
WHERE  d.customer_id IS NULL;

Hash-based change detection

In plaats van elke kolom expliciet te vergelijken, hash je alle SCD-2-attributen samen tot één hash_diff-kolom. Eén vergelijking en je weet of er iets veranderd is. Sneller, leesbaarder, minder bug-gevoelig.

Geavanceerde dimension-patronen

Junk dimension

Heb je een dozijn vlaggetjes en codes (is_promo, payment_method, channel) die niet de moeite waard zijn voor aparte dimensies? Stop ze in één junk dimension met alle mogelijke combinaties.

Role-playing dimension

Eén dim_date, maar meerdere keren joinen via aliassen (order_date, ship_date, delivery_date). Geen aparte tabellen nodig.

SELECT
    o.order_id,
    od.full_date AS order_date,
    sd.full_date AS ship_date,
    dd.full_date AS delivery_date
FROM   fact_order_lifecycle o
JOIN   dim_date od ON od.date_key = o.date_placed_key
JOIN   dim_date sd ON sd.date_key = o.date_shipped_key
JOIN   dim_date dd ON dd.date_key = o.date_delivered_key;

Bridge table (multi-valued)

Een patiënt heeft meerdere diagnoses, een product heeft meerdere kleuren. Een bridge-tabel met weight factors lost dit op zonder rijen te dubbeltellen.

CREATE TABLE bridge_product_color (
  product_key   INT,
  color_key     INT,
  weight_factor DECIMAL(5,4)    -- som per product = 1.0
);

Mini-dimensie

Snel-wisselende attributen (klant-credit-rating, marketing-segment) trekken SCD-2 explosies aan. Zet ze in een aparte mini-dimensie en koppel die direct aan de fact, niet aan de hoofd-dimensie.

Conformed dimensions

Een dimensie is conformed als hij identiek wordt gebruikt over meerdere fact tables. dim_date is altijd conformed. dim_customer hoort dat te zijn, maar wordt het vaak niet als verschillende teams hun eigen versie bouwen. Conformed dimensions zijn de enige manier om consistent te aggregeren over business processen heen.

Factless fact tables

Soms is "het feit dat iets gebeurde" zelf het feit, zonder meetwaarde. Een student die zich inschrijft voor een cursus, een klant die een nieuwsbrief opent. Een factless fact bevat alleen foreign keys, en wordt geteld in plaats van gesommeerd.

Surrogate keys versus natural keys

Bron-IDs (customer_id = 'C-12345') lijken een goede primary key voor je dimensie. Tot een SAP-migratie de IDs hernummert, een fusie twee klantenbestanden samenvoegt, of een leverancier zijn productcodes uitbreidt van 6 naar 8 tekens. Surrogate keys — een synthetische integer of hash, alleen zinvol binnen het warehouse — beschermen je tegen al deze pijn.

Conventies die zich in de praktijk uitbetalen:

Late-arriving dimensies en facts

De werkelijkheid is wreed: soms komt een verkoop binnen voordat de bijbehorende klant in de klantdimensie staat. Of een SCD2-update arriveert dagen na de fact die ernaar verwijst. Twee patronen lossen dit op:

De grain — de heiligste regel

Voor je een fact-tabel ontwerpt: bepaal de grain — wat één rij voorstelt. "Eén rij per orderregel per dag." Schrijf het op. Mix nooit grains. Een fact die soms één rij per order is en soms één per orderregel is een tijdbom voor aggregaties.

Key takeaways

  • Star > snowflake voor leesbaarheid en performance.
  • Drie fact-types: transaction, periodic snapshot, accumulating snapshot — elk voor een ander doel.
  • SCD Type 2 met hash-based change detection is de moderne standaard.
  • Junk, role-playing, bridge en mini-dimensies lossen specifieke problemen op.
  • Conformed dimensions maken cross-process analyses mogelijk.
  • Definieer altijd je grain — en wijk er nooit van af.
  • Documenteer of metingen additief, semi-additief of niet-additief zijn.

Veelgestelde vragen

Wat is het verschil tussen een star schema en een snowflake schema?

In een star schema is elke dimensie één gedenormaliseerde tabel die direct aan de fact-tabel hangt. In een snowflake schema worden dimensies verder genormaliseerd in subtabellen. Star is sneller voor query engines, simpeler voor BI-tools en de praktische standaard in moderne cloud-warehouses.

Wat is een Slowly Changing Dimension?

Een SCD is een dimensie waarvan de attributen in de loop van de tijd wijzigen. Kimball definieert zeven types die elk een andere strategie hebben: Type 1 overschrijft, Type 2 maakt een nieuwe rij per wijziging met valid_from/valid_to, Type 3 bewaart de vorige waarde in een extra kolom. In de praktijk gebruik je vooral Type 1 en Type 2.

Wat is een fact table?

Een fact table bevat metingen of gebeurtenissen — verkopen, klikken, voorraadstanden — met foreign keys naar dimensietabellen voor context. Er zijn drie hoofdtypen: transaction facts (één rij per gebeurtenis), periodic snapshot facts (één rij per entiteit per periode) en accumulating snapshot facts (één rij per business proces dat door fasen gaat).

Wat is een conformed dimension?

Een conformed dimension is een dimensie die identiek wordt gebruikt over meerdere fact tables — dim_date of dim_customer met dezelfde definities en surrogate keys, ongeacht of je hem joint met fact_sales of fact_inventory. Conformed dimensions zijn de enige manier om consistent te aggregeren over business processen heen.

Wat is de grain van een fact table?

De grain is wat één rij in de fact table voorstelt — bijvoorbeeld 'één rij per orderregel per dag'. Bepaal de grain vooraf, schrijf hem op en wijk er nooit van af. Een fact-tabel met gemixte grains maakt elke aggregatie onbetrouwbaar.

Wanneer gebruik je een bridge table?

Een bridge table los je multi-valued dimensions mee op — situaties waar één entiteit meerdere waarden in een andere kan hebben (een patiënt met meerdere diagnoses, een product met meerdere kleuren). De bridge bevat de relatie plus een weight_factor zodat aggregaties niet dubbeltellen.