Zusammenfassung
Dimensionstabellen verändern sich im Laufe der Zeit. SCDs beschreiben, wie solche Änderungen in einer Datenpipeline behandelt werden. Bei SCD Typ 1 werden Werte überschrieben, bei Typ 2 wird die Historie mitgeführt. dbt unterstützt Typ 2 nativ über dbt snapshots. Dieser Beitrag behandelt die dbt Snapshots, Typen 1 bis 4, legt den Schwerpunkt aber auf Typ 2.
In meinem letzten Blogbeitrag habe ich gezeigt, wie ein minimales dbt Projekt aussieht. Nun geht es darum, wie ein konkretes Konzept aus dem Data Engineering typischerweise in dbt umgesetzt wird.
Im Paradigma des dimensionalen Modellierens nach Ralph Kimball wird zwischen zwei zentralen Tabellentypen unterschieden, Faktentabellen und Dimensionstabellen. Faktentabellen enthalten in der Regel Ereignisse, die über die Zeit stattfinden und daher häufig ein Datum oder einen Zeitstempel enthalten. Dimensionstabellen liefern den fachlichen Kontext zu diesen Fakten, zum Beispiel Kundennamen, Produktkategorien oder Filialstandorte. Dadurch lassen sich sehr große Faktentabellen effizient in der Datenbank abfragen, während Analyst:innen später gezielt nur die zusätzlichen Felder aus den Dimensionstabellen hinzunehmen, die tatsächlich benötigt werden.
Dieser Prozess, bei dem sich wiederholende Attribute in eigene Tabellen ausgelagert und anschließend über Schlüssel referenziert werden, wird Normalisierung genannt. Das bedeutet allerdings auch, dass eine Änderung in einer Dimensionstabelle, etwa wenn ein Kunde in eine andere Stadt zieht oder ein Produkt neu klassifiziert wird, den Kontext aller referenzierenden Faktensätze stillschweigend verändert. Deshalb muss festgelegt werden, wie mit solchen Änderungen in den Daten umgegangen werden soll. Sollen alte Werte überschrieben und damit verworfen werden oder sollen beide Versionen erhalten bleiben? Genau dieses Problem adressieren Slowly Changing Dimensions.
Welche SCD-Typen gibt es und wann ist welcher Typ sinnvoll?
SCDs gibt es in verschiedenen Ausprägungen, die Änderungen im Data Warehouse unterschiedlich behandeln oder nachverfolgen. Alle haben ihren jeweiligen Anwendungsfall:
| Typ | Was bei einer Änderung passiert | Historie | Wann sinnvoll |
|---|---|---|---|
| 1 | Der alte Wert wird überschrieben | Keine | Wenn nur der aktuelle Stand relevant ist |
| 2 | Eine neue Zeile wird eingefügt und die alte geschlossen, z.B. über valid_to, end_date, is_current |
Vollständig | Wenn nachvollziehbar bleiben soll, wie etwas zu einem bestimmten Zeitpunkt aussah |
| 3 | Eine Spalte wie previous_value wird ergänzt |
Eine vorherige Version | Selten, zum Beispiel in Übergangsphasen |
| 4 | Aktuelle Daten und Historie werden in getrennten Tabellen gehalten | Vollständig, aber getrennt | Leistungsorientierte Dashboards |
Daneben gibt es auch SCD Typ 0, bei dem nie etwas aktualisiert wird, sowie Typ 5 und 6 als Mischformen anderer Typen. Diese Varianten sind jedoch deutlich spezieller. SCD Typ 1 ist im Kern ein einfaches Überschreiben. Typ 2 ist der am häufigsten genutzte SCD Typ und erfordert eine gezielte Umsetzung. Wie also lassen sich Typ 1 und Typ 2 in dbt implementieren?
Wie implementierst du SCD Type 1 mit einem inkrementellen Modell?
Das ist der einfachste Fall. Ein Attribut ändert sich, der neue Wert soll übernommen werden und die alte Version wird nicht mehr benötigt. Das ist sinnvoll bei Korrekturen, etwa bei einem Tippfehler im Kundennamen, bei Attributen, deren alter Wert keine Relevanz mehr hat, etwa bei einer aktuellen E-Mail-Adresse, oder wenn Speicherbedarf, Kosten und Komplexität wichtiger sind als historische Genauigkeit. In dbt lässt sich das über ein inkrementelles Modell mit Merge-Strategie umsetzen.

In diesem Beispiel passiert bei jedem Durchlauf Folgendes:
- Zeilen, die nicht neuer sind als der letzte bekannte Stand in der Zieltabelle, werden ignoriert, also solche mit einem
updated_at, das nicht größer ist als das zuletzt bekannteupdated_at - Eingehende Zeilen werden über
customer_idzugeordnet und vorhandene Werte mit den neuen Daten überschrieben
Das ist sauber, speichereffizient und funktioniert in Snowflake und BigQuery ohne zusätzliche Anpassungen. Eine Historie wird dabei allerdings nicht gespeichert. Außerdem werden keine Zeilen gelöscht, nur weil sie in den eingehenden Daten nicht mehr vorhanden sind. Durch {{ this }} kann ein Modell auf sich selbst verweisen, ohne dass dbt dies als zirkuläre Referenz wertet.
Wie verfolgst du die volle Historie mit dbt-Snapshots (SCD Type 2)?
Ohne Historie lassen sich Fragen wie „Welche Adresse hatte dieser Kunde im Januar?“ nicht beantworten. Mit einer Architektur nach SCD Typ 2 ist genau das möglich. SCD Typ 2 erhält die Historie, indem neue Zeilen angelegt und Kennzeichnungen gesetzt werden. Wenn sich ein Attribut ändert, wird die aktuelle Zeile geschlossen, etwa über end_date oder valid_to oder zusätzlich über ein Flag wie is_current. Anschließend wird eine neue Zeile mit dem aktualisierten Wert eingefügt. Beide Versionen bleiben erhalten. So lässt sich zu jedem Zeitpunkt nachvollziehen, wie die Dimension ausgesehen hat.
Das ist überall dort sinnvoll, wo historischer Kontext für Analysen wichtig ist, etwa bei geänderten Kategorien, früheren Produktpreisen oder Anpassungen von Abonnementmodellen. Allgemein gilt, wenn eine Faktentabelle auf diese Dimension verweist und die richtige Antwort davon abhängt, wann ein Ereignis stattgefunden hat, liefert Typ 2 die nötige Grundlage.
dbt bringt für SCD Typ 2 eine eingebaute Funktionalität mit, und zwar über Snapshots, die in produktiven Umgebungen vor dbt run ausgeführt werden können. Snapshots sind .sql oder .yml Dateien im Verzeichnis snapshots/. Sie sind keine Modelle und werden mit dbt snapshot statt mit dbt run gestartet:

Die Snapshot Tabelle liest aus der Quelle und schreibt in eine eigene Tabelle customer_snapshot im Schema snapshots, siehe target_schema, die ihren Zustand selbst verwaltet. Wenn nun dbt snapshot ausgeführt wird, vergleicht dbt die beiden Tabellen, indem es über customer_id matched und prüft, ob sich updated_at verändert hat. Für jede Zeile, in der sich etwas geändert hat, zum Beispiel address oder department, setzt dbt in der bestehenden Zeile dbt_valid_to auf den updated_at Wert der eingehenden Zeile und fügt gleichzeitig eine neue Zeile mit dbt_valid_from und dbt_valid_to = NULL ein.
Anstelle von nur einer Zeile pro Kunde in der Quelltabelle enthält die resultierende Snapshot Tabelle nun eine Zeile pro Version eines Kunden. Dadurch bleibt die vollständige Historie aller Änderungen über die Zeit hinweg erhalten. Zusätzlich zu dbt_valid_from und dbt_valid_to werden auch dbt_scd_id als eindeutiger Surrogatschlüssel, zum Beispiel aus customer_id und updated_at, sowie dbt_updated_at zur Nachverfolgung der Snapshot Durchläufe ergänzt.
Was wird außerdem konfiguriert?
unique_key– Das Feld, an dem eingehende und vorhandene Zeilen abgeglichen werden (entspricht dem SCD Typ 1 Merge)updated_at– Legt fest, welche Spalte dbt für den Vergleich von Zeitstempeln verwendetinvalidate_hard_deletes– Wenn ein Kunde vollständig aus der Quelle verschwindet, schließt dbt die zuletzt gültige Zeile, statt sie fälschlicherweise als weiterhin aktuell bestehen zu lassen
Strategien zur Änderungserkennung (strategy)
timestamp: Vergleicht die Spalteupdated_at. Ist der Zeitstempel neuer, gilt die Zeile als verändert. Das ist effizienter, setzt aber einen verlässlichen Zeitstempel in der Quelle voraus.check: Vergleicht die Spaltenwerte direkt, entweder alle oder eine definierte Auswahl. Änderungen werden dadurch auch ohne Zeitstempel erkannt, der Ansatz ist aber aufwendiger, weil jede Zeile wertbasiert verglichen wird.
Als Faustregel gilt: timestamp ist die bessere Wahl, wenn updated_at in der Quelle verlässlich gepflegt wird. check eignet sich dann, wenn es keinen solchen Zeitstempel gibt oder wenn die Quelle lediglich ein täglich überschriebenes Flat File mit dem jeweils aktuellen Zustand liefert, aber ohne Change Data Capture oder sonstige Änderungsinformationen.
Wie lässt sich der frühere Stand eines Kunden abfragen?
Abfrage des Zustands zu einem bestimmten Zeitpunkt:
Hier zeigt sich der Nachteil von SCD Typ 2. Da mehrere Versionen eines Datensatzes vorhanden sind, muss in Abfragen explizit angegeben werden, welcher Zustand gemeint ist. Um den Stand eines Kunden zu einem bestimmten Zeitpunkt zu erhalten, muss entsprechend auf den gültigen Zeitraum gefiltert werden:

Und wenn nur der aktuelle Zustand benötigt wird, etwa für Dashboards ohne Historienbezug, wird auf die aktuell gültigen Zeilen gefiltert:

Alternativ lässt sich auch ein is_current Flag verwenden, wenn dieses entsprechend eingerichtet wurde.
Wann lohnen sich tägliche Snapshots statt eines klassischen Type 2?
Um die typische Logik mit WHERE dbt_valid_to IS NULL und den zusätzlichen Versionierungsaufwand in nachgelagerten Abfragen zu vermeiden, kann auch ein anderer Weg gewählt werden. Statt Änderungen aktiv zu erkennen und neue Versionen anzulegen, kann die gesamte Dimensionstabelle täglich als Snapshot gespeichert werden. Dabei wird jeden Tag eine vollständige Kopie in eine neue Datumspartition geschrieben:

Der Vorteil ist, dass damit dieselbe fachliche Fragestellung beantwortet werden kann wie mit Typ 2, zum Beispiel „Wie sah der Katalog letzten Dienstag aus?“, ohne dass überhaupt Logik zur Änderungserkennung nötig ist. Der Nachteil liegt im Speicherbedarf, da auch unveränderte Zeilen täglich erneut gespeichert werden. Für einen Produktkatalog mit 50.000 Zeilen ist das meist unproblematisch, bei Tabellen mit Millionen Zeilen summiert es sich deutlich.
Wann tägliche Snapshots statt eines klassischen SCD Typ 2 sinnvoll ist
- Die Dimension ist klein, also deutlich unter mehreren Millionen Zeilen
- Die Quelle hat keinen verlässlichen
updated_atZeitstempel - Faktentabellen sind bereits nach Datum partitioniert und können entsprechend mit
fact.dt = dim.snapshot_dateverknüpft werden - Einfachheit ist wichtiger als Speichereffizienz
Wann ein klassisches SCD Typ 2 vorzuziehen ist
- Wenn die Dimension groß ist aber Änderungen nur punktuell auftreten
- Es soll nachvollziehbar bleiben, welche Attribute sich wann geändert haben
- Nachgelagerte Modelle benötigen ohnehin Felder wie
valid_fromundvalid_to
SCD Typ 3 & 4: Kurze Erwähnungen
Was ist SCD Type 3 und wann brauchst du ihn? Typ 3 ergänzt statt einer neuen Zeile lediglich eine Spalte wie previous_value und speichert damit genau eine vorherige Version. Das kann zum Beispiel in Reorganisationsphasen sinnvoll sein, etwa bei einem Wechsel von alter zu neuer Region während einer Übergangszeit. In der Praxis lässt sich dieser Zustand jedoch meist auch aus einer Typ 2 Tabelle mit einer Window Funktion wie LAG() ableiten.
Was ist SCD Type 4 und wofür eignet er sich? Typ 4 trennt aktuellen Stand und Historie in zwei physisch getrennte Tabellen. Die Hauptdimension bleibt schlank, typischerweise mit Typ 1 Logik und schnellen Joins, während eine separate Historientabelle jede Änderung speichert. Das ist besonders dann hilfreich, wenn für Dashboards vor allem der aktuelle Zustand performant verfügbar sein muss und Filter wie WHERE dbt_valid_to IS NULL vermieden werden sollen. In dbt lässt sich Typ 4 sauber umsetzen, indem ein Snapshot für die Historie verwendet und darüber eine View auf den aktuellen Stand gelegt wird.
Welche SCD-Variante solltest du für Dein Projekt wählen?
In dbt läuft die Entscheidung häufig auf folgende Logik hinaus:
- Wird eine Historie benötigt?
Nein → Dann reicht Typ 1 mit inkrementellem Merge. - Ja, und die Dimension ist überschaubar groß und verfügt über ein verlässliches
updated_at?
Dann ist Typ 2 die passende Wahl. - Ja, aber die Quelle ist unstrukturiert und klein, und auf Logik zur Änderungserkennung soll möglichst verzichtet werden?
Dann können tägliche Snapshots die einfachere Lösung sein.
Insgesamt hilft dbt dabei, auf komplexe Stored Procedures oder manuell gepflegte Surrogatschlüssel zu verzichten, etwa in Form eines Hashes aus customer_id und valid_from. Ein Konfigurationsblock in einer Snapshot Datei abstrahiert genau diese Komplexität. Es muss lediglich festgelegt werden, was verfolgt und wie Änderungen erkannt werden sollen.
Man könnte also sagen, dass sich SCD Typ 2 von einer aufwendig zu wartenden Lösung zu “Warum eigentlich nicht, zumindest für Dimensionen, bei denen Historie fachlich relevant ist?” gewandelt hat.
Jetzt Beratungsgespräch vereinbaren
Ob wachsende Anforderungen, komplexe Datenflüsse oder der Wunsch nach mehr Transparenz und Wiederverwendbarkeit:
Wir unterstützen Euch dabei, dbt so einzusetzen, dass daraus ein tragfähiges Fundament für moderne und skalierbare Datenprozesse entsteht. Lasst uns darüber sprechen, wie wir Euch bei Eurem dbt Vorhaben unterstützen können.


