Data Modeling mit dbt — Von rohen Daten zu analytischen ModellenData Modeling with dbt — From Raw Data to Analytical Models

Prolog: SQL als Transformationssprache

Rohe Daten sind wertlos, solange sie nicht transformiert, getestet und dokumentiert werden. dbt (data build tool) macht SQL zur Transformationssprache: Statt Python-Skripte oder ETL-Tools baust du Modelle in reinem SQL, versionierst sie in Git und testest sie automatisch. dbt löst das „T" in ELT — die Transformation findet direkt im Data Warehouse statt, nicht in externen Prozessen.

In diesem Artikel bauen wir ein komplettes dbt-Projekt auf: von der Quellanbindung über das Layer-Modell bis zur automatisierten Dokumentation und inkrementellen Modellen.

Kapitel 1: Die Transformation Layer — Vom Chaos zur Ordnung

Ein dbt-Projekt folgt dem Layer-Modell: Rohdaten fließen durch vier Schichten, jede mit klar definierter Verantwortung. Sources (die Rohquellen — Datenbanken, APIs, CSV-Dateien), Staging (Bereinigung: Spalten umbenennen, Typen casten, Duplikate entfernen), Intermediate (Geschäftslogik: Joins, Aggregationen, Window Functions) und Marts (analytische Modelle: Facts und Dimensions für Dashboards und Reporting).

dbt Transformation Layer Architecture
Abb. 1: Die vierschichtige dbt-Architektur. Raw Layer (Sources: PostgreSQL, REST API, CSV Files, S3 Bucket) → Staging Layer (stg_: Rename, Cast, Deduplicate, Filter) → Intermediate Layer (int_: Joins, Business Logic, Aggregations, Window Funcs) → Marts Layer (fct_, dim_: analytische Modelle). Jede Schicht hat eine klare Verantwortung — keine Geschäftslogik in Staging, keine Bereinigung in Marts.

Die Namenskonvention ist entscheidend: stg_orders statt orders_cleaned, int_customer_orders statt joined_data_v3, fct_orders statt final_orders_table. Jeder, der das Projekt öffnet, versteht sofort die Struktur. Konvention schlägt Dokumentation — wenn die Namen richtig sind, braucht man weniger Kommentare.

Kapitel 2: Modelle und Materialisierungen

Jede SQL-Datei in dbt ist ein Modell. Aber nicht jedes Modell wird gleich materialisiert. dbt bietet vier Strategien: View (Standard — wird bei jeder Abfrage neu berechnet, ideal für Staging), Table (wird als physische Tabelle geschrieben, gut für Marts), Incremental (nur neue Zeilen verarbeiten — kritisch für große Tabellen) und Ephemeral (wird als CTE inline — kein physisches Objekt, ideal für Zwischenberechnungen).

dbt Model Types & Materializations
Abb. 2: Links: Verteilung der Modelltypen — Source (8), Staging (12), Intermediate (18), Mart Fact (6), Mart Dim (5). Rechts: Materialisierungsstrategien — View (31%), Table (24%), Incremental (16%), Ephemeral (29%). Staging als View, Marts als Table, große Fakten inkrementell, Zwischenberechnungen ephemeral.

Die richtige Materialisierung spart Compute-Kosten und Build-Zeit. Faustregel: Staging-Modelle als View (kein Storage-Overhead), Marts als Table (schnelle Dashboard-Abfragen), Faktentabellen >10M Zeilen als Incremental (nur Delta verarbeiten), und Hilfsmodelle als Ephemeral (keine unnötigen Objekte im Warehouse). Die Konfiguration geschieht in dbt_project.yml oder direkt im Modell via {{ config(materialized='incremental') }}.

Kapitel 3: Testing — Datenqualität automatisiert prüfen

Tests sind das Herzstück von dbt. Jedes Modell sollte getestet werden — nicht gegen Bugs im Code (das macht die SQL-Engine), sondern gegen Datenqualitätsprobleme. dbt bietet vier eingebaute Tests: unique (keine Duplikate im Primary Key), not_null (keine fehlenden Werte), accepted_values (nur erlaubte Werte, z.B. Status-Felder) und relationships (Fremdschlüssel-Integrität).

dbt Testing Framework
Abb. 3: Links: Testtypen und Anzahl — not_null (62), unique (45), relationships (24), accepted_values (18), custom SQL (12). Rechts: Test-Coverage-Wachstum über 6 Monate — von 35% auf 92% Coverage, bei steigender Gesamtzahl (40 → 161 Tests). Ziel: 90%+ Coverage für alle Mart-Modelle.

Praxis-Tipps für dbt-Tests: (1) Jeder Primary Key braucht unique und not_null — keine Ausnahmen. (2) relationships-Tests für alle Fremdschlüssel — fängt gebrochene Joins vor dem Dashboard ab. (3) Custom Tests in SQL für komplexe Geschäftsregeln — z.B. „Bestellsumme darf nicht negativ sein." (4) Schema-Tests in schema.yml definieren, nicht in separaten Dateien. (5) dbt test in die CI/CD-Pipeline einbinden — kein Merge ohne grüne Tests.

Kapitel 4: Dokumentation und Lineage — Der Daten-Stammbaum

dbt generiert automatisch eine Lineage-Grafik (DAG — Directed Acyclic Graph), die zeigt, welches Modell von welchen anderen abhängt. Vom Source durch Staging und Intermediate bis zum Mart — jede Abhängigkeit ist sichtbar. Das ist nicht nur schön, sondern operativ kritisch: Wenn eine Quelle ausfällt, siehst du sofort, welche Downstream-Modelle betroffen sind.

dbt Lineage Graph (DAG)
Abb. 4: Lineage Graph eines dbt-Projekts. Sources (src_orders, src_customers, src_products) → Staging (stg_orders, stg_customers, stg_products) → Intermediate (int_order_items, int_customer_orders) → Marts (fct_orders, dim_customers). Jeder Pfeil ist eine Abhängigkeit — ref('stg_orders') im SQL erzeugt den Link. Farbcodierung nach Layer: blau (Sources), violett (Staging), grün (Intermediate), orange (Marts).

Die Dokumentation lebt in schema.yml-Dateien neben den Modellen: Beschreibungen für Modelle, Spalten und Tests. dbt docs generate erzeugt eine interaktive Website mit Suchfunktion, Lineage-Explorer und Spalten-Details. Tipp: Beschreibe nicht was die Spalte heißt (das sieht man), sondern warum sie existiert und wie sie berechnet wird. total_revenue → „Summe aller bezahlten Bestellpositionen, exklusive Stornierungen und Retouren."

Kapitel 5: Inkrementelle Modelle — Große Datenmengen effizient verarbeiten

Bei kleinen Tabellen (<1M Zeilen) ist ein Full Refresh schnell genug. Bei großen Faktentabellen (10M, 50M, 100M+ Zeilen) wird Full Refresh zum Flaschenhals — jedes dbt run verarbeitet Millionen Zeilen, die sich nicht geändert haben. Inkrementelle Modelle lösen das: Sie verarbeiten nur neue oder geänderte Zeilen seit dem letzten Run.

Incremental Models — Performance Comparison
Abb. 5: Links: Runtime-Vergleich über 12 Monate. Full Refresh: von 30s auf 360s (linearer Anstieg mit Datenvolumen). Incremental: von 30s auf 45s (nahezu konstant). Die Ersparnis wächst exponentiell mit der Datengröße. Rechts: Verarbeitete Zeilen pro Run — bei 100M Gesamtzeilen verarbeitet Incremental nur 150k statt 100M (Log-Skala).

Implementierung: {{ config(materialized='incremental', unique_key='order_id') }} im Modell. Der unique_key definiert, wie Duplikate behandelt werden (MERGE statt INSERT). Der Filter: {% if is_incremental() %} WHERE updated_at > (SELECT max(updated_at) FROM {{ this }}) {% endif %}. Wichtig: Inkrementelle Modelle sind nicht idempotent — ein dbt run --full-refresh pro Woche als Safety Net einplanen. Late-arriving Data mit einem Lookback-Fenster von 3 Tagen abfangen.

Kapitel 6: Projekt-Metriken — Das dbt-Dashboard

Ein reifes dbt-Projekt wird anhand von sechs Metriken gemessen: Modelle pro Layer, Test-Pass-Rate, Build-Zeit, Dokumentations-Coverage, Source Freshness und Run-Frequenz. Diese Metriken zeigen nicht nur den aktuellen Zustand, sondern auch den Reifegrad deines Data-Engineering-Setups.

dbt Project Metrics Dashboard
Abb. 6: dbt-Projekt-Dashboard mit sechs Metriken. (1) Modelle pro Layer: 8 Sources, 12 Staging, 18 Intermediate, 11 Marts. (2) Test-Pass-Rate: von 88% auf 98% über 8 Wochen. (3) Build-Zeit: von 12.5 auf 5.2 Minuten. (4) Dokumentations-Coverage: Models 92%, Columns 82%, Sources 100%, Tests 96%. (5) Source Freshness: alle unter SLA. (6) Monthly Runs: 45 CI/CD, 30 Scheduled, 12 Manual, 4 Full Refresh.

Ziele für ein produktionsreifes Projekt: (1) Test-Pass-Rate >95% — kein Deployment mit fehlschlagenden Tests. (2) Build-Zeit <10 Minuten — durch inkrementelle Modelle und selektive Runs (dbt run -s +fct_orders). (3) Dokumentation >90% für alle Mart-Modelle und deren Spalten. (4) Source Freshness innerhalb des SLA — dbt source freshness als täglicher Check. (5) CI/CD-Integration: dbt run und dbt test bei jedem Merge auf den main-Branch.

Epilog: dbt ist der Goldstandard für Data Transformation

dbt hat die Art verändert, wie Data Engineers arbeiten: SQL als Transformationssprache, Git als Versionskontrolle, Tests als Qualitätsgarantie, und Dokumentation als Nebenprodukt. Die Einstiegshürde ist niedrig — wer SQL kann, kann dbt lernen. Der Payoff ist hoch: reproduzierbare, getestete, dokumentierte Datenmodelle, die jeder im Team versteht. Kein Python nötig, keine ETL-Tools, keine Black Boxes.

Zitationen

  • dbt Labs (2024). dbt Documentation. docs.getdbt.com
  • Kimball, R. & Ross, M. (2013). The Data Warehouse Toolkit. 3rd Edition. Wiley.
  • Reis, J. & Housley, M. (2022). Fundamentals of Data Engineering. O'Reilly.
  • dbt Labs (2024). Best Practices Guide. docs.getdbt.com/best-practices
  • Seastrom, C. (2023). How we structure our dbt projects. discourse.getdbt.com

Fazit

Ein dbt-Projekt transformiert Rohdaten in analytische Modelle: Layer-Architektur (Sources → Staging → Intermediate → Marts), Materialisierungen (View, Table, Incremental, Ephemeral), automatisierte Tests (unique, not_null, relationships, custom), Lineage-Dokumentation (DAG), inkrementelle Modelle (für große Tabellen) und Projekt-Metriken (Build-Zeit, Coverage, Freshness). Jedes Kapitel ist ein konkreter Baustein — zusammen ergeben sie ein produktionsreifes Data-Modeling-Setup.

Dokumentation

ParameterWert
Tooldbt Core (Open Source)
WarehousePostgreSQL / DuckDB (lokal)
Modelle gesamt49 (8 Sources, 12 Staging, 18 Int, 11 Marts)
Tests gesamt161 (62 not_null, 45 unique, 24 rel, 18 accepted, 12 custom)
Test-Coverage92%
Build-Zeit5.2 Minuten (inkrementell)
MaterialisierungView (31%), Table (24%), Incremental (16%), Ephemeral (29%)
Dokumentation>90% für Marts

Prologue: SQL as a Transformation Language

Raw data is worthless until it's transformed, tested, and documented. dbt (data build tool) makes SQL the transformation language: Instead of Python scripts or ETL tools, you build models in pure SQL, version them in Git, and test them automatically. dbt solves the "T" in ELT — transformation happens directly in the data warehouse, not in external processes.

In this article, we build a complete dbt project: from source connections through the layer model to automated documentation and incremental models.

Chapter 1: The Transformation Layer — From Chaos to Order

A dbt project follows the layer model: Raw data flows through four tiers, each with clearly defined responsibilities. Sources (raw origins — databases, APIs, CSV files), Staging (cleansing: rename columns, cast types, remove duplicates), Intermediate (business logic: joins, aggregations, window functions), and Marts (analytical models: facts and dimensions for dashboards and reporting).

dbt Transformation Layer Architecture
Fig. 1: The four-layer dbt architecture. Raw Layer (sources: PostgreSQL, REST API, CSV files, S3 bucket) → Staging Layer (stg_: rename, cast, deduplicate, filter) → Intermediate Layer (int_: joins, business logic, aggregations, window funcs) → Marts Layer (fct_, dim_: analytical models). Each layer has a clear responsibility — no business logic in staging, no cleansing in marts.

Naming conventions are critical: stg_orders instead of orders_cleaned, int_customer_orders instead of joined_data_v3, fct_orders instead of final_orders_table. Anyone opening the project immediately understands the structure. Convention beats documentation — when names are right, you need fewer comments.

Chapter 2: Models and Materializations

Every SQL file in dbt is a model. But not every model is materialized the same way. dbt offers four strategies: View (default — recomputed on every query, ideal for staging), Table (written as a physical table, good for marts), Incremental (process only new rows — critical for large tables), and Ephemeral (inlined as CTE — no physical object, ideal for intermediate calculations).

dbt Model Types & Materializations
Fig. 2: Left: Model type distribution — Source (8), Staging (12), Intermediate (18), Mart Fact (6), Mart Dim (5). Right: Materialization strategies — View (31%), Table (24%), Incremental (16%), Ephemeral (29%). Staging as views, marts as tables, large facts incremental, helper calculations ephemeral.

The right materialization saves compute costs and build time. Rule of thumb: Staging models as View (no storage overhead), marts as Table (fast dashboard queries), fact tables >10M rows as Incremental (process only deltas), and helper models as Ephemeral (no unnecessary objects in the warehouse). Configuration happens in dbt_project.yml or directly in the model via {{ config(materialized='incremental') }}.

Chapter 3: Testing — Automated Data Quality Checks

Tests are the heart of dbt. Every model should be tested — not against code bugs (the SQL engine handles that), but against data quality problems. dbt offers four built-in tests: unique (no duplicates in the primary key), not_null (no missing values), accepted_values (only allowed values, e.g., status fields), and relationships (foreign key integrity).

dbt Testing Framework
Fig. 3: Left: Test types and counts — not_null (62), unique (45), relationships (24), accepted_values (18), custom SQL (12). Right: Test coverage growth over 6 months — from 35% to 92% coverage, with increasing total count (40 → 161 tests). Target: 90%+ coverage for all mart models.

Practical tips for dbt tests: (1) Every primary key needs unique and not_null — no exceptions. (2) relationships tests for all foreign keys — catches broken joins before the dashboard. (3) Custom tests in SQL for complex business rules — e.g., "order total must not be negative." (4) Define schema tests in schema.yml, not in separate files. (5) Integrate dbt test into the CI/CD pipeline — no merge without green tests.

Chapter 4: Documentation and Lineage — The Data Family Tree

dbt automatically generates a lineage graph (DAG — Directed Acyclic Graph) showing which model depends on which others. From source through staging and intermediate to mart — every dependency is visible. This isn't just pretty, it's operationally critical: When a source goes down, you immediately see which downstream models are affected.

dbt Lineage Graph (DAG)
Fig. 4: Lineage graph of a dbt project. Sources (src_orders, src_customers, src_products) → Staging (stg_orders, stg_customers, stg_products) → Intermediate (int_order_items, int_customer_orders) → Marts (fct_orders, dim_customers). Every arrow is a dependency — ref('stg_orders') in SQL creates the link. Color-coded by layer: blue (sources), purple (staging), green (intermediate), orange (marts).

Documentation lives in schema.yml files alongside the models: descriptions for models, columns, and tests. dbt docs generate creates an interactive website with search, lineage explorer, and column details. Tip: Don't describe what the column is called (you can see that), describe why it exists and how it's calculated. total_revenue → "Sum of all paid order line items, excluding cancellations and returns."

Chapter 5: Incremental Models — Processing Large Data Efficiently

For small tables (<1M rows), a full refresh is fast enough. For large fact tables (10M, 50M, 100M+ rows), full refresh becomes a bottleneck — every dbt run processes millions of rows that haven't changed. Incremental models solve this: They process only new or changed rows since the last run.

Incremental Models — Performance Comparison
Fig. 5: Left: Runtime comparison over 12 months. Full refresh: from 30s to 360s (linear increase with data volume). Incremental: from 30s to 45s (nearly constant). Savings grow exponentially with data size. Right: Rows processed per run — at 100M total rows, incremental processes only 150k instead of 100M (log scale).

Implementation: {{ config(materialized='incremental', unique_key='order_id') }} in the model. The unique_key defines how duplicates are handled (MERGE instead of INSERT). The filter: {% if is_incremental() %} WHERE updated_at > (SELECT max(updated_at) FROM {{ this }}) {% endif %}. Important: Incremental models are not idempotent — schedule a dbt run --full-refresh weekly as a safety net. Catch late-arriving data with a 3-day lookback window.

Chapter 6: Project Metrics — The dbt Dashboard

A mature dbt project is measured by six metrics: models per layer, test pass rate, build time, documentation coverage, source freshness, and run frequency. These metrics show not just the current state but also the maturity level of your data engineering setup.

dbt Project Metrics Dashboard
Fig. 6: dbt project dashboard with six metrics. (1) Models per layer: 8 sources, 12 staging, 18 intermediate, 11 marts. (2) Test pass rate: from 88% to 98% over 8 weeks. (3) Build time: from 12.5 to 5.2 minutes. (4) Documentation coverage: models 92%, columns 82%, sources 100%, tests 96%. (5) Source freshness: all within SLA. (6) Monthly runs: 45 CI/CD, 30 scheduled, 12 manual, 4 full refresh.

Targets for a production-ready project: (1) Test pass rate >95% — no deployment with failing tests. (2) Build time <10 minutes — through incremental models and selective runs (dbt run -s +fct_orders). (3) Documentation >90% for all mart models and their columns. (4) Source freshness within SLA — dbt source freshness as a daily check. (5) CI/CD integration: dbt run and dbt test on every merge to the main branch.

Epilogue: dbt Is the Gold Standard for Data Transformation

dbt has changed how data engineers work: SQL as the transformation language, Git as version control, tests as quality guarantee, and documentation as a byproduct. The entry barrier is low — if you know SQL, you can learn dbt. The payoff is high: reproducible, tested, documented data models that everyone on the team understands. No Python needed, no ETL tools, no black boxes.

Citations

  • dbt Labs (2024). dbt Documentation. docs.getdbt.com
  • Kimball, R. & Ross, M. (2013). The Data Warehouse Toolkit. 3rd Edition. Wiley.
  • Reis, J. & Housley, M. (2022). Fundamentals of Data Engineering. O'Reilly.
  • dbt Labs (2024). Best Practices Guide. docs.getdbt.com/best-practices
  • Seastrom, C. (2023). How we structure our dbt projects. discourse.getdbt.com

Conclusion

A dbt project transforms raw data into analytical models: Layer architecture (Sources → Staging → Intermediate → Marts), materializations (View, Table, Incremental, Ephemeral), automated tests (unique, not_null, relationships, custom), lineage documentation (DAG), incremental models (for large tables), and project metrics (build time, coverage, freshness). Each chapter is a concrete building block — together they form a production-ready data modeling setup.

Documentation

ParameterValue
Tooldbt Core (open source)
WarehousePostgreSQL / DuckDB (local)
Total Models49 (8 sources, 12 staging, 18 int, 11 marts)
Total Tests161 (62 not_null, 45 unique, 24 rel, 18 accepted, 12 custom)
Test Coverage92%
Build Time5.2 minutes (incremental)
MaterializationView (31%), Table (24%), Incremental (16%), Ephemeral (29%)
Documentation>90% for marts
No track selected

Click play to start