Inhaltsverzeichnis19 Abschnitte
- TL;DR
- Schnellreferenz
- Das Setup: Eine Kundendimension
- SCD Typ 1: Überschreiben
- SCD Typ 2: Neue Zeile anfügen
- Abfragen gegen SCD Typ 2
- Typ 2 in dbt: Snapshots
- SCD Typ 3: Neue Spalte hinzufügen
- SCD Typ 4: History-Tabelle
- Welcher Typ ist der richtige?
- Was ist mit Typ 0, 5, 6 und 7?
- SCDs erkunden ohne Warehouse
- Fazit
- FAQ
- Welcher SCD-Typ ist Standard bei dbt?
- Brauche ich für DSGVO-Audit-Trails immer SCD Typ 2?
- Wann ist SCD Typ 4 besser als Typ 2?
- Kann ich SCD-Typen pro Spalte mischen?
- Weiterlesen
Slowly Changing Dimensions (SCD) erklärt: Typ 1 bis 4 mit SQL-Beispielen
Dimensionstabellen beschreiben das "Wer, Was, Wo" deiner Daten — Kunden, Produkte, Mitarbeitende, Standorte. Das Problem: Die Realität ändert sich. Kunden ziehen um, Produkte werden umbenannt, Mitarbeitende wechseln die Abteilung. Wie du diese Änderungen in deinem Warehouse abbildest, entscheidet, ob historische Reports die Wahrheit sagen oder still lügen.
Slowly Changing Dimensions (SCDs) sind die Techniken, mit denen du genau diese Änderungen in Dimensionstabellen verwaltest. Das Konzept stammt aus der dimensionalen Modellierung nach Ralph Kimball und ist bis heute der praktischste Rahmen für Data-Warehouse-Design. Dieser Guide deckt SCD Typ 1, 2, 3 und 4 mit konkreten SQL-Beispielen und ehrlichen Trade-offs ab.
TL;DR
- SCD Typ 1 überschreibt — keine Historie, einfach, aber gefährlich für Reports
- SCD Typ 2 legt für jede Änderung eine neue Zeile an — der Workhorse für Analytics mit historischer Genauigkeit
- SCD Typ 3 fügt eine "previous"-Spalte hinzu — funktioniert nur für eine Generation Historie
- SCD Typ 4 trennt Current State und History in zwei Tabellen — gut für sehr große Dimensionen
- Default-Empfehlung: SCD Typ 2 mit dbt-Snapshots deckt 90 % der Fälle ab
Schnellreferenz
| Typ | Strategie | Historische Daten | Storage-Kosten | Komplexität |
|---|---|---|---|---|
| Typ 1 | Überschreiben | Verloren | Niedrig | Niedrig |
| Typ 2 | Neue Zeile anfügen | Vollständig erhalten | Hoch | Hoch |
| Typ 3 | Neue Spalte hinzufügen | Begrenzt erhalten | Mittel | Mittel |
| Typ 4 | Separate History-Tabelle | Vollständig erhalten | Mittel-Hoch | Mittel |
Das Setup: Eine Kundendimension
Wir nutzen durchgängig eine dim_customer-Tabelle. Eine Kundin startet in Berlin, zieht dann nach München. Ihre Loyalty-Stufe steigt von Silber auf Gold. Wie behandelt jeder SCD-Typ diese Änderungen?
Anfangszustand:
| customer_key | customer_id | name | city | loyalty_tier | updated_at |
|---|---|---|---|---|---|
| 1 | C-100 | Anna Müller | Berlin | Silber | 2024-01-10 09:00:00 |
SCD Typ 1: Überschreiben
Typ 1 überschreibt schlicht den alten Wert mit dem neuen. Keine Historie. Die Dimension spiegelt immer den aktuellen Zustand.
-- PostgreSQL: SCD Type 1 — overwrite on change
UPDATE dim_customer
SET
city = 'Munich',
loyalty_tier = 'Gold',
updated_at = NOW()
WHERE customer_id = 'C-100';
Nach dem Update:
| customer_key | customer_id | name | city | loyalty_tier | updated_at |
|---|---|---|---|---|---|
| 1 | C-100 | Anna Müller | München | Gold | 2024-03-15 14:30:00 |
Der Berlin-Eintrag ist weg. Alle Faktentabellen-Zeilen, die auf diese Dimension verweisen, zeigen Anna jetzt fälschlicherweise als München-Kundin über ihre gesamte Historie — auch für Bestellungen, die sie aus Berlin gemacht hat.
Wann du Typ 1 nutzen solltest:
- Der alte Wert ist tatsächlich bedeutungslos (z. B. Korrektur eines Tippfehlers)
- Analyst:innen haben explizit zugestimmt, dass historische Genauigkeit für dieses Attribut egal ist
- Storage ist extrem knapp
Wann du Typ 1 nicht nutzen solltest:
- Für jedes Attribut, bei dem "Was war zum Transaktionszeitpunkt wahr?" zählt
- Für compliance-sensible Daten, die einen Audit-Trail brauchen (DSGVO!)
SCD Typ 2: Neue Zeile anfügen
Typ 2 ist der Workhorse der dimensionalen Modellierung. Statt zu überschreiben, läufst du die alte Zeile ab und fügst eine neue Zeile für den aktualisierten Zustand ein. Jede Zeile bekommt Effective- und Expiry-Date-Spalten plus ein Boolean-Flag is_current.
-- PostgreSQL: SCD Type 2 — expire old row, insert new row
-- Step 1: Expire the current row
UPDATE dim_customer
SET
valid_to = '2024-03-15',
is_current = FALSE
WHERE customer_id = 'C-100'
AND is_current = TRUE;
-- Step 2: Insert the new current row
INSERT INTO dim_customer
(customer_key, customer_id, name, city, loyalty_tier,
valid_from, valid_to, is_current, updated_at)
VALUES
(DEFAULT, 'C-100', 'Anna Müller', 'Munich', 'Gold',
'2024-03-15', '9999-12-31', TRUE, NOW());
Nach dem Update:
| customer_key | customer_id | name | city | loyalty_tier | valid_from | valid_to | is_current |
|---|---|---|---|---|---|---|---|
| 1 | C-100 | Anna Müller | Berlin | Silber | 2024-01-10 | 2024-03-15 | FALSE |
| 2 | C-100 | Anna Müller | München | Gold | 2024-03-15 | 9999-12-31 | TRUE |
Jetzt joint eine Faktenzeile aus dem Januar korrekt auf customer_key = 1 (Berlin, Silber). Eine Faktenzeile aus dem April joint auf customer_key = 2 (München, Gold). Die Historie bleibt exakt erhalten.
Abfragen gegen SCD Typ 2
Für Current-State-Queries filterst du auf is_current = TRUE. Für Point-in-Time-Queries joinst du über den Datumsbereich:
-- PostgreSQL: point-in-time join against SCD Type 2 dimension
SELECT
f.order_id,
f.order_date,
f.amount_usd,
c.city AS customer_city_at_time,
c.loyalty_tier AS loyalty_tier_at_time
FROM fact_orders f
JOIN dim_customer c
ON f.customer_id = c.customer_id
AND f.order_date BETWEEN c.valid_from AND c.valid_to
WHERE f.order_date >= '2024-01-01';
Wann du Typ 2 nutzen solltest:
- Attribute, bei denen historische Genauigkeit zählt (Geografie, Stufe, Status)
- Regulatorische oder Compliance-Anforderungen für Audit-Trails (z. B. DSGVO)
- Customer-Journey- und Kohortenanalysen
Trade-offs:
- Die Dimensionstabelle wächst mit jeder Änderung — bei großem Volumen erheblich
- Queries müssen mit mehreren Zeilen pro Entität umgehen können
- ETL-Logik ist komplexer und fehleranfälliger
- Foreign Keys in Faktentabellen zeigen auf Surrogate Keys, nicht auf Natural Keys
Typ 2 in dbt: Snapshots
dbt hat First-Class-Support für SCD Typ 2 über das snapshot-Feature. Du definierst die Strategie in einer Snapshot-Datei und dbt übernimmt die Expire-/Insert-Logik automatisch:
# dbt snapshots/customers_snapshot.sql
{% snapshot customers_snapshot %}
{{
config(
target_schema='snapshots',
unique_key='customer_id',
strategy='timestamp',
updated_at='updated_at'
)
}}
SELECT
customer_id,
name,
city,
loyalty_tier,
updated_at
FROM {{ source('raw', 'customers') }}
{% endsnapshot %}
Führst du dbt snapshot aus, generiert dbt die Spalten valid_from, valid_to, dbt_scd_id und dbt_is_current automatisch. Das eliminiert die fehleranfälligsten Teile einer SCD-Typ-2-Implementierung.
SCD Typ 3: Neue Spalte hinzufügen
Typ 3 erhält eine begrenzte Historie, indem eine "Previous-Value"-Spalte neben dem aktuellen Wert hinzukommt. Es ist ein Mittelweg — du bekommst eine Generation Historie ohne die Komplexität mehrerer Zeilen.
-- PostgreSQL: SCD Type 3 — add previous_city column
ALTER TABLE dim_customer
ADD COLUMN previous_city VARCHAR(100),
ADD COLUMN city_changed_at TIMESTAMPTZ;
-- Update: shift current to previous, set new current
UPDATE dim_customer
SET
previous_city = city,
city = 'Munich',
city_changed_at = NOW(),
updated_at = NOW()
WHERE customer_id = 'C-100'
AND is_current = TRUE;
Nach dem Update:
| customer_key | customer_id | name | city | previous_city | loyalty_tier |
|---|---|---|---|---|---|
| 1 | C-100 | Anna Müller | München | Berlin | Gold |
Wann du Typ 3 nutzen solltest:
- Du brauchst "Aktuell vs. vorheriger Wert"-Reports ohne komplette Historie
- Das Attribut ändert sich selten und Analyst:innen interessieren sich nur für einen historischen Wert
- Du willst die Komplexität von Typ 2 vermeiden
Wann du Typ 3 nicht nutzen solltest:
- Wenn sich Attribute öfter als zweimal ändern (das Muster bricht — du bräuchtest
previous_previous_city) - Wenn Point-in-Time-Genauigkeit über beliebige Daten gebraucht wird
Typ 3 wird in der Praxis unterschätzt. Er löst ein spezifisches Reporting-Muster sauber, generalisiert aber nicht.
SCD Typ 4: History-Tabelle
Typ 4 trennt Current State und History komplett. Die Haupt-Dimensionstabelle hält nur die aktuelle Zeile (wie Typ 1), während eine separate History-Tabelle jede vergangene Version aufnimmt.
-- PostgreSQL: SCD Type 4 — separate history table
-- Current state table (always one row per entity)
CREATE TABLE dim_customer_current (
customer_key BIGSERIAL PRIMARY KEY,
customer_id VARCHAR(20) NOT NULL UNIQUE,
name VARCHAR(200),
city VARCHAR(100),
loyalty_tier VARCHAR(20),
updated_at TIMESTAMPTZ
);
-- History table (one row per historical version)
CREATE TABLE dim_customer_history (
history_key BIGSERIAL PRIMARY KEY,
customer_id VARCHAR(20) NOT NULL,
name VARCHAR(200),
city VARCHAR(100),
loyalty_tier VARCHAR(20),
valid_from TIMESTAMPTZ NOT NULL,
valid_to TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- On change: archive current to history, then update current
BEGIN;
INSERT INTO dim_customer_history
(customer_id, name, city, loyalty_tier, valid_from, valid_to)
SELECT customer_id, name, city, loyalty_tier, updated_at, NOW()
FROM dim_customer_current
WHERE customer_id = 'C-100';
UPDATE dim_customer_current
SET city = 'Munich', loyalty_tier = 'Gold', updated_at = NOW()
WHERE customer_id = 'C-100';
COMMIT;
Vorteile von Typ 4:
- Die Current-State-Tabelle bleibt klein und schnell für den Standardfall
- Historie ist isoliert und kann unabhängig abgefragt oder archiviert werden
- Query-Performance für nicht-historische Use-Cases ist deutlich besser als bei Typ 2
Nachteile:
- Queries, die sowohl aktuelle als auch historische Daten brauchen, erfordern Joins über zwei Tabellen
- Komplexere ETL — Änderungen müssen atomar über zwei Tabellen koordiniert werden
Wann du Typ 4 nutzen solltest:
- Sehr große Dimensionen, bei denen Typ 2 erhebliche Performance-Probleme verursachen würde
- Wenn die meisten Queries nur den Current State brauchen und historische Queries selten sind
- Wenn du eine klare physische Trennung zwischen operativen und historischen Daten willst
Welcher Typ ist der richtige?
Dieser Entscheidungsrahmen deckt die meisten Praxisfälle ab:
- "War der alte Wert schlicht falsch?" → Typ 1 (Fehlerkorrektur)
- "Zählt historische Genauigkeit für dieses Attribut?" → Typ 2
- "Brauchen wir nur den Vergleich aktuell vs. eine vorherige Version?" → Typ 3
- "Ist die Dimension riesig und die meisten Queries brauchen nur aktuelle Daten?" → Typ 4
- "Brauchen wir sowohl Performance als auch volle Historie?" → Typ 4 (Hybrid: Current-Tabelle für schnelle Queries, History-Tabelle für Point-in-Time)
In der Praxis nutzen die meisten Dimensionstabellen Typ 2 für sich langsam ändernde Attribute wie Geografie, Status, Kategorie — und Typ 1 für Attribute, bei denen Korrekturen die Norm sind und Historie irrelevant ist.
Was ist mit Typ 0, 5, 6 und 7?
Kimball hat zusätzliche Typen definiert, die in der Praxis selten sind:
- Typ 0: Ändert sich nie (Geburtsdatum o. Ä.)
- Typ 5: Typ 1 plus Mini-Dimension für hochkardinale Attribute
- Typ 6: Kombiniert Typ 1 + 2 + 3 (aktueller Wert in aktueller Zeile UND in einer "current_X"-Spalte auf historischen Zeilen)
- Typ 7: Duale Foreign Keys — einer auf Typ 1 (current), einer auf Typ 2 (historisch) — erlaubt beide Query-Stile gegen dieselbe Faktentabelle
Typ 6 und 7 lohnen sich, wenn du mit komplexen dimensionalen Modellen arbeitest, aber für die meisten Teams deckt Typ 2 (mit dbt-Snapshots) 90 % der Fälle ab.
SCDs erkunden ohne Warehouse
Arbeitest du mit SCD-Daten, die als Flat Files landen — exportierte Snapshots, dbt-Snapshot-CSV-Outputs, Archiv-Dumps — kannst du sie mit Harbinger Explorer direkt im Browser per DuckDB WASM abfragen. Du kannst mehrere CSV-Dateien für verschiedene Snapshot-Daten hochladen und SQL-Joins darüber laufen lassen, um Point-in-Time-Views zu rekonstruieren — ohne Warehouse-Umgebung.
Fazit
SCD Typ 2 ist der Default für die meisten Dimensionsattribute, bei denen Historie zählt. Er erhält Point-in-Time-Genauigkeit, kombiniert sauber mit dbt-Snapshots und gibt Analyst:innen das vollständige Bild für Kohorten- und Journey-Analysen. Typ 1 ist richtig für Korrekturen. Typ 3 und 4 lösen spezifische Edge-Cases. Wichtig ist die bewusste Wahl pro Attribut — nicht "Überschreiben, weil einfacher".
Für den breiteren Kontext, wo Dimensionstabellen in deine Datenarchitektur passen, lies Medallion Architecture Explained und Data Lakehouse Architecture Explained.
FAQ
Welcher SCD-Typ ist Standard bei dbt?
dbt-Snapshots implementieren SCD Typ 2 standardmäßig. Du wählst die Strategie (timestamp oder check) und dbt generiert die Spalten valid_from, valid_to, dbt_scd_id und dbt_is_current automatisch.
Brauche ich für DSGVO-Audit-Trails immer SCD Typ 2?
Nicht zwingend, aber meistens ja. Wenn du nachweisen musst, welcher Datenstand zu einem bestimmten Zeitpunkt galt (z. B. Einwilligungsstatus, Adressdaten, Profilattribute), ist Typ 2 oder Typ 4 sinnvoll. Typ 1 (Überschreiben) zerstört den Audit-Trail.
Wann ist SCD Typ 4 besser als Typ 2?
Bei sehr großen Dimensionen (zig Millionen Zeilen) und überwiegend Current-State-Queries. Typ 4 hält die heiße Tabelle klein und schiebt die Historie in eine kalte Tabelle, die du seltener queryst und unabhängig archivieren kannst.
Kann ich SCD-Typen pro Spalte mischen?
Ja, das ist sogar empfohlen. Innerhalb einer Dimension kannst du z. B. name als Typ 1 (Korrekturen) und loyalty_tier als Typ 2 (Historie wichtig) führen. Das ist konzeptionell SCD Typ 6.
Weiterlesen
Stand: 15. Mai 2026.
Geschrieben von
Harbinger Team
Cloud-, Data- und AI-Engineer in DACH. Schreibt seit 2018 über infrastrukturkritische Tech-Entscheidungen — keine Marketing- Folien, sondern echte Trade-offs aus Production-Workloads.
Hat dir das geholfen?
Jede Woche ein neuer Artikel über DACH-Cloud, Data und AI — direkt in dein Postfach. Kein Spam, kein Marketing-Sprech.
Kein Spam. 1-Klick-Abmeldung. Datenschutz bei Loops.so.