Wat zijn Slowly Changing Dimensions?
In elk data warehouse veranderen dimensiegegevens over tijd. Een klant verhuist, een medewerker krijgt een nieuwe functietitel, een product wisselt van categorie. De vraag is: hoe sla je die veranderingen op? Overschrijf je de oude waarde? Bewaar je de historie? Of doe je iets daartussenin?
Slowly Changing Dimensions (SCD) is het raamwerk uit de dimensionele modellering (Kimball-methode) dat precies dit probleem oplost. Er zijn 7 typen (Type 0 t/m Type 6), elk met een eigen strategie voor het omgaan met veranderingen in dimensietabellen.
SCD in het kort
Slowly Changing Dimensions zijn technieken om veranderingen in dimensiedata (klanten, producten, medewerkers) te verwerken in een data warehouse, variërend van het negeren van wijzigingen (Type 0) tot een complete hybride aanpak met volledige historie en actuele waarde (Type 6).
SCD Type 0 — Retain Original (Niet Wijzigen)
Bij Type 0 worden attributen nooit bijgewerkt na de initiële opslag. De waarde op het moment van eerste invoer blijft voor altijd staan, ongeacht latere wijzigingen in het bronsysteem.
Wanneer gebruiken?
- Attributen die per definitie niet mogen veranderen (geboortedatum, BSN, originele registratiedatum)
- Wettelijke of audit-vereisten waarbij de oorspronkelijke waarde bewaard moet blijven
- Referentiedata die als vaststaand wordt beschouwd
-- Type 0: Geen update op originele waarden
-- De kolom geboortedatum wordt nooit geüpdatet
INSERT INTO dim_klant (klant_id, naam, geboortedatum, registratiedatum)
SELECT klant_id, naam, geboortedatum, CURRENT_DATE
FROM staging_klant s
WHERE NOT EXISTS (
SELECT 1 FROM dim_klant d WHERE d.klant_id = s.klant_id
);
Tip
Type 0 wordt vaak gecombineerd met andere SCD-typen. Binnen dezelfde dimensietabel kan de ene kolom Type 0 zijn (geboortedatum) en een andere kolom Type 2 (adres).
SCD Type 1 — Overschrijven
Type 1 is de eenvoudigste aanpak: bij een wijziging wordt de oude waarde overschreven met de nieuwe. Er is geen historie beschikbaar — je hebt altijd alleen de meest actuele waarde.
Wanneer gebruiken?
- Correcties van fouten (typfout in een naam)
- Attributen waarvan de historie niet relevant is voor rapportages
- Eenvoud boven volledigheid — kleine dimensies zonder auditverplichtingen
-- Type 1: Overschrijf de oude waarde
UPDATE dim_klant
SET
email = s.email,
telefoonnummer = s.telefoonnummer
FROM staging_klant s
WHERE dim_klant.klant_id = s.klant_id
AND (dim_klant.email != s.email
OR dim_klant.telefoonnummer != s.telefoonnummer);
| Voordelen | Nadelen |
|---|---|
| Eenvoudig te implementeren | Geen historie beschikbaar |
| Geen tabelgroei | Historische rapportages geven verkeerde resultaten |
| Altijd actuele waarde | Onmogelijk om trends in dimensiedata te analyseren |
SCD Type 2 — Historische Rijen
Type 2 is het meest gebruikte SCD-type in de praktijk. Bij elke wijziging wordt een nieuwe rij aangemaakt met de nieuwe waarden, terwijl de oude rij actief blijft met een einddatum. Zo heb je volledige historie van elke verandering.
Hoe werkt het?
Elke rij krijgt extra kolommen om de geldigheid bij te houden:
surrogate_key— unieke sleutel per versiegeldig_van— startdatum van deze versiegeldig_tot— einddatum (NULL of 9999-12-31 voor de huidige versie)is_actueel— boolean flag voor de actieve rij
-- Type 2: Sluit de oude rij af
UPDATE dim_klant
SET geldig_tot = CURRENT_DATE - INTERVAL '1 day',
is_actueel = FALSE
WHERE klant_id = 1001
AND is_actueel = TRUE;
-- Voeg de nieuwe versie in
INSERT INTO dim_klant (klant_sk, klant_id, naam, stad, geldig_van, geldig_tot, is_actueel)
VALUES (nextval('dim_klant_seq'), 1001, 'Jan de Vries', 'Rotterdam',
CURRENT_DATE, '9999-12-31', TRUE);
Voorbeeld: klant verhuist
| klant_sk | klant_id | naam | stad | geldig_van | geldig_tot | is_actueel |
|---|---|---|---|---|---|---|
| 101 | 1001 | Jan de Vries | Amsterdam | 2024-01-15 | 2026-04-18 | FALSE |
| 102 | 1001 | Jan de Vries | Rotterdam | 2026-04-19 | 9999-12-31 | TRUE |
Type 2 is de standaardkeuze wanneer je historische rapportages nodig hebt. Het nadeel is tabelgroei — bij veel wijzigingen groeit de dimensietabel snel. Lees ook onze SCD Type 2 implementatie in Databricks voor een praktische handleiding.
SCD Type 3 — Vorige en Huidige Waarde
Type 3 bewaart beperkte historie door een extra kolom toe te voegen voor de vorige waarde. Er wordt geen nieuwe rij aangemaakt — de tabel heeft een vaste breedte.
-- Type 3: Tabelstructuur met vorige waarde
ALTER TABLE dim_klant ADD COLUMN vorige_stad VARCHAR(100);
ALTER TABLE dim_klant ADD COLUMN stad_gewijzigd_op DATE;
-- Bij een verhuizing:
UPDATE dim_klant
SET vorige_stad = stad,
stad = 'Rotterdam',
stad_gewijzigd_op = CURRENT_DATE
WHERE klant_id = 1001;
Wanneer gebruiken?
- Je hebt alleen de vorige en huidige waarde nodig (niet de volledige historie)
- Vergelijking "voor en na" (bijv. reorganisatie: oude afdeling vs. nieuwe afdeling)
- Wanneer tabelgroei absoluut vermeden moet worden
| Voordelen | Nadelen |
|---|---|
| Geen tabelgroei (vaste rijcount) | Slechts 1 vorige waarde beschikbaar |
| Eenvoudige queries (geen joins op datums) | Niet schaalbaar naar meerdere historische wijzigingen |
| Geschikt voor eenvoudige voor/na-analyses | Extra kolommen per getrackt attribuut |
SCD Type 4 — Aparte Historietabel
Type 4 houdt de hoofddimensietabel compact (altijd alleen de actuele waarde) en verplaatst alle historie naar een aparte tabel. Dit geeft het beste van twee werelden: snelle lookups op de actuele data en volledige historie als dat nodig is.
-- Type 4: Hoofdtabel bevat alleen de actuele waarde
CREATE TABLE dim_klant (
klant_id INT PRIMARY KEY,
naam VARCHAR(100),
stad VARCHAR(100),
email VARCHAR(200)
);
-- Historietabel voor alle wijzigingen
CREATE TABLE dim_klant_historie (
historie_id SERIAL PRIMARY KEY,
klant_id INT,
naam VARCHAR(100),
stad VARCHAR(100),
email VARCHAR(200),
geldig_van DATE,
geldig_tot DATE
);
-- Bij wijziging: archiveer de oude waarde
INSERT INTO dim_klant_historie (klant_id, naam, stad, email, geldig_van, geldig_tot)
SELECT klant_id, naam, stad, email, geldig_van, CURRENT_DATE - INTERVAL '1 day'
FROM dim_klant WHERE klant_id = 1001;
-- Update de hoofdtabel
UPDATE dim_klant
SET stad = 'Rotterdam', geldig_van = CURRENT_DATE
WHERE klant_id = 1001;
Wanneer gebruiken?
- Performance-kritieke dimensietabellen waar de hoofdtabel compact moet blijven
- Grote dimensies met veel wijzigingen (bijv. miljoenen klanten die regelmatig verhuizen)
- Rapportages die vooral actuele data nodig hebben, met incidentele historische analyses
SCD Type 5 — Type 4 + Type 1 Mini-Dimensie
Type 5 combineert een mini-dimensie (Type 4) met een outrigger op de hoofddimensie. De mini-dimensie bevat snel-veranderende attributen, en de hoofddimensie heeft een foreign key naar de huidige mini-dimensie rij (Type 1 overschrijving).
-- Type 5: Mini-dimensie voor snel-veranderende attributen
CREATE TABLE dim_klant_profiel (
profiel_sk SERIAL PRIMARY KEY,
leeftijdsgroep VARCHAR(20),
inkomensklasse VARCHAR(20),
segment VARCHAR(50)
);
-- Hoofddimensie met outrigger naar actueel profiel
CREATE TABLE dim_klant (
klant_sk SERIAL PRIMARY KEY,
klant_id INT,
naam VARCHAR(100),
huidig_profiel_sk INT REFERENCES dim_klant_profiel(profiel_sk) -- Type 1: altijd actueel
);
-- Feitentabel verwijst naar het profiel op moment van transactie
CREATE TABLE feit_orders (
order_id INT,
klant_sk INT REFERENCES dim_klant(klant_sk),
profiel_sk INT REFERENCES dim_klant_profiel(profiel_sk), -- Profiel op dat moment
order_datum DATE,
bedrag DECIMAL(10,2)
);
Wanneer gebruiken?
- Dimensies met een mix van stabiele en snel-veranderende attributen
- Demografische data (leeftijdsgroep, inkomenssegment) die regelmatig verschuift
- Je wilt de feitentabel koppelen aan zowel het huidige als het historische profiel
SCD Type 6 — Hybride (1 + 2 + 3)
Type 6 is de meest complete aanpak en combineert Type 1, Type 2 en Type 3 in één tabel. Elke rij bevat de huidige waarde (Type 1), de historische waarde voor die versie (Type 2), en een kolom met de actuele waarde die in alle rijen wordt bijgewerkt (Type 3-achtig).
-- Type 6: Hybride aanpak (ook wel "Type 2+1+3" of "Hybrid SCD")
CREATE TABLE dim_klant (
klant_sk SERIAL PRIMARY KEY,
klant_id INT,
naam VARCHAR(100),
stad VARCHAR(100), -- Historische waarde (Type 2)
huidige_stad VARCHAR(100), -- Altijd actueel (Type 1 overwrite)
geldig_van DATE,
geldig_tot DATE,
is_actueel BOOLEAN
);
-- Bij verhuizing van Amsterdam naar Rotterdam:
-- Stap 1: Sluit de oude rij (Type 2)
UPDATE dim_klant
SET geldig_tot = CURRENT_DATE - INTERVAL '1 day',
is_actueel = FALSE,
huidige_stad = 'Rotterdam' -- Type 1: update in ALLE rijen
WHERE klant_id = 1001 AND is_actueel = TRUE;
-- Stap 2: Nieuwe rij (Type 2)
INSERT INTO dim_klant (klant_id, naam, stad, huidige_stad, geldig_van, geldig_tot, is_actueel)
VALUES (1001, 'Jan de Vries', 'Rotterdam', 'Rotterdam', CURRENT_DATE, '9999-12-31', TRUE);
-- Stap 3: Update huidige_stad in ALLE historische rijen (Type 1 overwrite)
UPDATE dim_klant
SET huidige_stad = 'Rotterdam'
WHERE klant_id = 1001;
Resultaat na verhuizing
| klant_sk | klant_id | stad | huidige_stad | geldig_van | geldig_tot | is_actueel |
|---|---|---|---|---|---|---|
| 101 | 1001 | Amsterdam | Rotterdam | 2024-01-15 | 2026-04-18 | FALSE |
| 102 | 1001 | Rotterdam | Rotterdam | 2026-04-19 | 9999-12-31 | TRUE |
De kolom stad geeft de waarde op dat moment, huidige_stad geeft altijd de laatste waarde — ideaal voor rapportages die beide perspectieven nodig hebben.
Wanneer gebruiken?
- Maximale flexibiliteit: je wilt zowel historische als actuele waarden in elke query
- Complexe rapportages die "wie was de klant toen?" en "wie is de klant nu?" combineren
- Business cases met zware audit- en compliancevereisten
Vergelijking: Welk SCD Type Kies Je?
Onderstaande tabel helpt je bij het kiezen van het juiste SCD-type voor jouw situatie.
| Kenmerk | Type 0 | Type 1 | Type 2 | Type 3 | Type 4 | Type 5 | Type 6 |
|---|---|---|---|---|---|---|---|
| Historie bewaard? | Nee | Nee | Volledig | 1 vorige | Volledig | Volledig | Volledig |
| Tabelgroei | Geen | Geen | Hoog | Geen | Laag (apart) | Laag (apart) | Hoog |
| Complexiteit | Laag | Laag | Middel | Laag | Middel | Hoog | Hoog |
| Actuele waarde? | Origineel | Ja | Via filter | Ja | Ja | Ja | Ja (kolom) |
| Gebruiksfrequentie | Laag | Hoog | Zeer hoog | Laag | Middel | Laag | Middel |
Beslisboom: Het Juiste SCD Type in 3 Vragen
Gebruik dit eenvoudige beslisschema om snel het juiste type te bepalen:
Vraag 1: Heb je historie nodig?
Nee → Type 0 (nooit wijzigen) of Type 1 (overschrijven).
Ja → Ga naar vraag 2.
Vraag 2: Hoeveel historie?
Alleen vorige waarde → Type 3.
Volledige historie → Ga naar vraag 3.
Vraag 3: Eén tabel of gescheiden?
Eén tabel, eenvoudig → Type 2.
Eén tabel, met actuele waarde erbij → Type 6.
Gescheiden tabellen → Type 4 of Type 5.
SCD in Moderne Data Platforms
In de praktijk worden SCD-patronen ondersteund door alle grote data platforms. Hier een overzicht van hoe je SCD implementeert in populaire tools:
Databricks / Delta Lake
Delta Lake's MERGE INTO maakt SCD Type 2 implementatie elegant en performant. Lees onze uitgebreide handleiding SCD Type 2 in Databricks.
MERGE INTO dim_klant AS target
USING staging_klant AS source
ON target.klant_id = source.klant_id AND target.is_actueel = TRUE
WHEN MATCHED AND target.stad != source.stad THEN
UPDATE SET geldig_tot = current_date(), is_actueel = FALSE
WHEN NOT MATCHED THEN
INSERT (klant_id, naam, stad, geldig_van, geldig_tot, is_actueel)
VALUES (source.klant_id, source.naam, source.stad, current_date(), '9999-12-31', TRUE);
dbt (Data Build Tool)
dbt heeft native SCD Type 2 support via snapshots. Je definieert een snapshot-strategie en dbt handelt de rest af:
-- dbt snapshot (models/snapshots/snap_klant.sql)
{% snapshot snap_klant %}
{{
config(
target_schema='snapshots',
unique_key='klant_id',
strategy='check',
check_cols=['naam', 'stad', 'email']
)
}}
SELECT * FROM {{ source('crm', 'klanten') }}
{% endsnapshot %}
Microsoft Fabric / Synapse
In Fabric kun je SCD implementeren via Dataflows Gen2 (visueel) of via Spark notebooks met Delta Lake — dezelfde MERGE-syntax als Databricks.
Veelgemaakte Fouten bij SCD
Na honderden SCD-implementaties te hebben gezien, zijn dit de meest voorkomende valkuilen:
Alles Type 2 maken
Niet elk attribuut heeft volledige historie nodig. Gebruik Type 1 voor correcties en Type 0 voor onveranderlijke velden. Combineer typen binnen dezelfde tabel.
Geen surrogate key
Bij Type 2 heb je een surrogate key nodig (klant_sk) naast de business key (klant_id). Zonder surrogate key werken joins met de feitentabel niet correct.
Datum-gaten in historie
Zorg dat geldig_tot van de oude rij aansluit op geldig_van van de nieuwe. Eén dag overlap of gap verstoort point-in-time queries volledig.
Conclusie
Slowly Changing Dimensions zijn een fundamenteel onderdeel van elk data warehouse. De keuze tussen de 7 typen hangt af van je business requirements: heb je historie nodig? Hoeveel? En hoe complex mag de implementatie zijn?
In de praktijk werken de meeste data warehouses met een combinatie van SCD-typen:
- Type 0 voor onveranderlijke attributen (geboortedatum, BSN)
- Type 1 voor correcties en niet-historische attributen
- Type 2 voor attributen met auditverplichtingen of rapportage-historie
- Type 6 voor complexe dimensies die zowel historische als actuele analyse vereisen
Begin altijd met het stellen van de juiste vraag: "Heeft de business behoefte aan historische analyse van dit attribuut?" Het antwoord bepaalt welk SCD-type de juiste keuze is.
Hulp Nodig bij Implementatie?
Zoek je een Data Engineer of advies over dit onderwerp?