Deutsch

SQL und Datenmodellierung, die über das erste Dashboard hinaus skaliert

Turn this article into takeaways for your work.

Each assistant summarizes the article only for you and suggests best practices for your work.

Ich habe jetzt drei solcher Data Warehouses geerbt, und die Diagnose ist immer dieselbe. Es gibt eine Abfrage, die jemand um 16 Uhr an einem Freitag in eine Slack-Direktnachricht eingefügt hat. Marketing hat ein Dashboard darüber gebaut. Sales hat ein weiteres Dashboard darüber gebaut. Sechs Monate später öffnet der CFO beide im selben Meeting, sieht zwei verschiedene Umsatzzahlen und vertraut dem Datenteam für den Rest des Quartals nicht mehr.

Das Problem ist nicht cleveres SQL. Es ist Disziplin: Schichten, Tests, Benennung und eine Außerbetriebnahme-Kadenz. Dieser Leitfaden ist diese Disziplin, geschrieben für Analysten, die bereits wissen, wie man Verknüpfungen macht, und die aufhören wollen, erreichbar zu sein für „warum sagt dieses Dashboard etwas anderes."

Die Slack-Thread-Abfrage

So beginnt es. Jemand in #revops stellt eine Frage. Sie schreiben eine 60-Zeilen-Abfrage gegen das Produktions-Replikat, legen die Antwort im Thread ab und machen weiter. Diese Abfrage wird in ein Mode-Notebook kopiert. Das Mode-Notebook wird zu einer Looker-Kachel. Die Looker-Kachel wird zu einem Board-Deck-Diagramm.

Neunzig Tage später ändert sich das zugrundeliegende Schema, die Abfrage bricht stillschweigend, und niemand merkt es, weil das Diagramm noch gerendert wird, nur mit schlechten Zahlen. Oder schlimmer: Die Abfrage bricht nie, hatte aber nie eine einzige benannte Definition von „aktiver Kunde", und jetzt starren drei Teams auf drei verschiedene Zählungen.

Die Slack-Thread-Abfrage ist in Ordnung als Slack-Thread-Abfrage. Das Problem ist der Weg von „Nachschlag" zu „tragendem Dashboard" ohne Prüfpunkt dazwischen. Sie brauchen einen Prüfpunkt. Der Prüfpunkt ist ein Modell.

Zwei Umsatzdefinitionen sind ein Vertrauensereignis

An dem Tag, an dem Finance und Product mit zwei verschiedenen Umsatzzahlen im selben Meeting auftauchen, haben Sie ein Jahr Vertrauen verloren und werden sechs Monate brauchen, um es zurückzugewinnen. Ich habe es bei drei verschiedenen Unternehmen beobachtet. Es ist kein Werkzeugproblem. Snowflake hat Sie nicht im Stich gelassen. dbt hat Sie nicht im Stich gelassen. Was versagt hat, ist, dass niemand die Entscheidung getroffen hat, was „Umsatz" bedeutet, bevor fünf Personen fünf Abfragen geschrieben haben.

Das ist verhütbares Engineering. Schichten und Tests verhindern das Zwei-Umsatz-Problem. Benennung und Außerbetriebnahme verhindern den Verfall des Data Warehouses. Die Arbeit ist absichtlich langweilig. Wenn sich Ihr Daten-Stack täglich aufregend anfühlt, brennt wahrscheinlich etwas.

SQL-Praktiken, die tatsächlich skalieren

Vier Gewohnheiten trennen Analysten, die ihr Data Warehouse übergeben können, von solchen, deren Code stirbt, sobald sie das Unternehmen verlassen.

CTEs statt verschachtelter Unterabfragen. Eine verschachtelte Unterabfrage ist ein Write-only-Artefakt. Sie können sie schreiben, aber sechs Wochen später können Sie sie nicht lesen, und die nächste Person auch nicht. CTEs ermöglichen es Ihnen, jeden Schritt zu benennen, jeden Schritt zu debuggen und über ein mentales Modell pro Block nachzudenken.

-- So nicht
SELECT customer_id, SUM(amount)
FROM (
  SELECT * FROM orders
  WHERE status = 'completed'
    AND created_at >= (SELECT MIN(created_at) FROM orders WHERE ...)
) o
JOIN (SELECT * FROM customers WHERE deleted_at IS NULL) c
  ON o.customer_id = c.id
GROUP BY 1;

-- So
WITH completed_orders AS (
  SELECT *
  FROM {{ ref('stg_shopify__orders') }}
  WHERE status = 'completed'
),

active_customers AS (
  SELECT *
  FROM {{ ref('stg_shopify__customers') }}
  WHERE deleted_at IS NULL
),

revenue_by_customer AS (
  SELECT
    c.customer_id,
    SUM(o.amount) AS lifetime_revenue
  FROM active_customers c
  LEFT JOIN completed_orders o
    ON o.customer_id = c.customer_id
  GROUP BY c.customer_id
)

SELECT * FROM revenue_by_customer;

Die zweite Version ist länger. Sie ist auch debuggbar. Sie können jede CTE markieren, sie allein ausführen und die Zeilenanzahl überprüfen. Das ist das ganze Spiel.

Benannte Tests auf jedem Modell. Nicht „ich füge Tests später hinzu." Auf jedem Modell, im selben PR. Die vier, die ihren Aufwand rechtfertigen:

# models/marts/_marts.yml
version: 2

models:
  - name: dim_customers
    columns:
      - name: customer_id
        tests:
          - unique
          - not_null
      - name: status
        tests:
          - accepted_values:
              values: ['active', 'churned', 'paused']
      - name: account_owner_id
        tests:
          - relationships:
              to: ref('dim_users')
              field: user_id

Vier Tests, fünfzehn Zeilen YAML, und Sie fangen neunzig Prozent der stillen Fehler ab, die sonst in einem Board-Deck auftauchen würden. unique und not_null auf jedem Primärschlüssel. accepted_values auf jeder Enum-artigen Spalte. relationships auf jedem Fremdschlüssel. Wenn Sie die Schlüssel nicht testen, haben Sie kein Modell. Sie haben eine Abfrage.

Ein einziges Modell als maßgebliche Datenquelle pro Geschäftsobjekt. Ein dim_customers. Ein fct_orders. Ein dim_users. Nicht dim_customers_v2, nicht dim_customers_marketing, nicht dim_customers_for_finance_q3. Wenn Marketing und Finance unterschiedliche Kundenschnitte brauchen, erhalten sie unterschiedliche Spalten auf demselben dim_customers, oder sie bauen Downstream-Marts darüber. Sie dürfen die Tabelle nicht klonen.

Der Klon ist der Weg, wie das Zwei-Umsatz-Problem entsteht. Auf der Stelle beenden.

Fensterfunktionen statt Self-Joins. Wenn Sie eine Tabelle mit sich selbst verknüpfen, um „vorheriges Bestelldatum" oder „Rang innerhalb eines Kunden" zu berechnen, arbeiten Sie zu hart. LAG(), LEAD(), ROW_NUMBER() und RANK() erledigen dieselbe Arbeit in einem Durchgang mit halb so vielen Zeilen und einem Zehntel der Warehouse-Kosten.

Datenmodellierungsschichten: Staging, Intermediate, Marts

Das ist der Teil, den niemand tun möchte, weil er sich wie zusätzliche Arbeit anfühlt. Es ist keine zusätzliche Arbeit. Es ist die Arbeit, die alles danach möglich macht.

Staging (stg_). Ein Modell pro Quelltabelle. Spalten in die Konvention Ihres Projekts umbenennen. Typen umwandeln. Nichts weiter. Keine Verknüpfungen, keine Geschäftslogik, keine Aggregationen. Die Aufgabe der Staging-Schicht ist es, jedem Downstream-Modell eine saubere, vorhersehbare, benannte Ansicht einer Quelltabelle zu geben. Wenn Sie Staging überspringen, weil „es nur eine Umbenennung ist", werden Sie es an dem Tag bereuen, an dem Stripe eine Spalte umbenennt oder Ihre Fivetran-Synchronisation die Groß-/Kleinschreibung ändert.

-- models/staging/stripe/stg_stripe__charges.sql
WITH source AS (
  SELECT * FROM {{ source('stripe', 'charges') }}
),

renamed AS (
  SELECT
    id              AS charge_id,
    customer        AS customer_id,
    amount / 100.0  AS amount_usd,
    status          AS charge_status,
    created::TIMESTAMP AS charged_at
  FROM source
)

SELECT * FROM renamed;

Das ist das gesamte Modell. Es ist langweilig. Das ist der Punkt.

Intermediate (int_). Hier leben Verknüpfungen, Geschäftslogik und wiederverwendbare Bausteine. Intermediate-Modelle werden niemals für BI freigegeben. Sie existieren, um Ihre Mart-Modelle lesbar zu halten und Teile wiederverwenden zu können. Wenn drei Marts alle „monatlich wiederkehrender Umsatz pro Kunde" benötigen, ist das ein int_customer_mrr_monthly-Modell, einmal gebaut, dreimal referenziert.

-- models/intermediate/int_customer_mrr_monthly.sql
WITH subscriptions AS (
  SELECT * FROM {{ ref('stg_stripe__subscriptions') }}
),

months AS (
  SELECT * FROM {{ ref('int_calendar_months') }}
),

active_in_month AS (
  SELECT
    m.month_start,
    s.customer_id,
    s.monthly_amount_usd
  FROM months m
  LEFT JOIN subscriptions s
    ON m.month_start BETWEEN s.started_at AND COALESCE(s.canceled_at, '9999-12-31')
)

SELECT
  customer_id,
  month_start,
  SUM(monthly_amount_usd) AS mrr_usd
FROM active_in_month
GROUP BY 1, 2;

Marts (fct_ / dim_). Das ist das, was BI-Tools abfragen. Denormalisiert, geschäftslesbar und so benannt, wie eine Führungskraft sie benennen würde. fct_ für Faktentabellen (Bestellungen, Buchungen, Sessions). dim_ für Dimensionstabellen (Kunden, Produkte, Mitarbeiter). Ihre Mart-Schicht ist die API Ihres Data Warehouses. Behandeln Sie Änderungen daran wie API-Änderungen: versioniert, kommuniziert, mit Ankündigung außer Betrieb genommen.

Ein zehnköpfiges Datenteam endet typischerweise mit 200 bis 400 Modellen über diese drei Schichten. Grob 50 bis 60 Prozent Staging, 25 bis 30 Prozent Intermediate, 15 bis 20 Prozent Marts. Wenn Ihre Mart-Anzahl über dieses Verhältnis ansteigt, betreiben Sie Geschäftslogik in Marts, die in Intermediate liegen sollte.

Die „Zwei Umsatzdefinitionen"-Diagnose

Das können Sie in zwanzig Minuten durchführen. Tun Sie es, bevor Sie ein neues Modell in einem geerbten Data Warehouse schreiben.

  1. Rufen Sie jede Dashboard-Kachel, jeden BI-Bericht und jede gespeicherte Abfrage ab, die das Wort „Umsatz" im Titel oder in der Spalte hat.
  2. Kopieren Sie das SQL für jede in ein einzelnes Dokument.
  3. Vergleichen Sie die WHERE-Klauseln, die Verknüpfungsbedingungen und die SUM-Ziele.
  4. Erstellen Sie eine Tabelle: Dashboard-Name, Eigentümer, Definition, Quelle.
  5. Gehen Sie damit zu Finance. Lassen Sie sich eine Definition schriftlich bestätigen.
  6. Bauen Sie ein fct_revenue-Modell, das der bestätigten Definition entspricht.
  7. Migrieren Sie jedes Dashboard zu diesem Modell. Nehmen Sie die alten Abfragen außer Betrieb.

Die meisten Teams finden beim ersten Audit drei bis fünf verschiedene Umsatzdefinitionen. Manchmal sind sie alle für einen engen Zweck „richtig", aber nur eine ist die, die der CFO auf einem Earnings Call verteidigen würde. Diese lebt im Modell. Die anderen werden als Views darüber neu geschrieben (vw_revenue_marketing_attributed, vw_revenue_booking_basis), damit klar ist, dass es sich um Schnitte derselben Zahl handelt, nicht um separate Zahlen.

Führen Sie dasselbe Audit für „aktiver Kunde", „Abwanderung", „ARR" und „MRR" durch. Sie werden dasselbe Problem finden. Beheben Sie es auf dieselbe Weise.

dbt-Namenskonventionen

Benennung ist kein Kleinkram. Es ist die Art, wie der nächste Analyst sich in Ihrem Data Warehouse orientiert, ohne Ihnen eine Frage stellen zu müssen.

  • stg_{quelle}__{entität}. Doppelter Unterstrich trennt Quelle von Entität. stg_stripe__charges, stg_hubspot__contacts, stg_shopify__orders. Der doppelte Unterstrich ist das visuelle Zeichen, dass die linke Seite ein Systemname ist.
  • int_{entität}_{verb}. Beschreibt, was das Modell tut. int_orders_pivoted, int_customers_enriched, int_sessions_attributed.
  • fct_{ereignis} und dim_{entität}. Beispiele: fct_orders, fct_charges, fct_page_views, dim_customers, dim_products, dim_dates.
  • Quell-Aliasing in Tests, nicht Spalten-Aliasing in Mart-SQL. Ihr dim_customers.customer_id sollte customer_id sein, nicht dim_customers__customer_id.

Das _v2-Suffix ist ein Warnsignal. Wenn Sie dim_customers_v2.sql neben dim_customers.sql sehen, hat jemand Angst gehabt, das Original zu verändern, und eine Abspaltung geliefert. Versionierung gehört in Git, nicht in den Dateinamen. Der richtige Schritt ist: dim_customers neu schreiben, die brechende Änderung mit einer Migrationsnotiz liefern, das alte Verhalten terminiert außer Betrieb nehmen. Beide dauerhaft zu behalten garantiert, dass das Zwei-Umsatz-Problem mit einem anderen Namen zurückkehrt.

Versionskontrolle und Changelog-Disziplin

Keine direkten Merges auf main. Niemals. Auch nicht für den einzeiligen Tippfehler-Fix. In dem Moment, in dem Sie eine Ausnahme machen, wird sie zur Regel.

Jeder PR hat einen einzeiligen CHANGELOG-Eintrag in CHANGELOG.md:

## 2026-04-29
- `fct_orders`: `is_first_order`-Spalte hinzugefügt. Betrifft: marketing-attribution, lifecycle-emails Dashboards. (@camellia)
- `breaking: dim_customers`: `acct_owner` → `account_owner_id` umbenannt. Betrifft: alle CRM-Dashboards. Migration: siehe #1284. (@camellia)
- `stg_stripe__charges`: NULL `customer_id` aus Gast-Checkouts behandelt. (@camellia)

Drei Regeln für den CHANGELOG:

  1. Jeder PR fügt einen Eintrag hinzu. Kein PR wird ohne einen gemergt. CI kann das durchsetzen.
  2. Schema-Änderungen werden mit breaking: markiert und in #data-changes gepingt, bevor der PR gemergt wird.
  3. Das „betrifft"-Feld nennt Dashboards oder Downstream-Modelle, keine vagen Phrasen wie „verschiedene Berichte". Wenn Sie nicht benennen können, was es betrifft, kennen Sie Ihren Downstream-Graphen nicht gut genug zum Mergen.

Schema-Änderungen erhalten auch eine terminierte Außerbetriebnahme. Die alte Spalte bleibt 30 Tage mit einem Kommentar bestehen, die neue Spalte wird parallel hinzugefügt, Downstream-Konsumenten migrieren, dann wird die alte Spalte entfernt. Keine Ausnahmen für „einfache" Umbenennungen. Einfache Umbenennungen sind die Art, wie Sie herausfinden, dass jemand einen Zapier-Flow auf Ihr Data Warehouse gebaut hat, ohne Sie zu informieren.

Dashboard versus Einzel-Anfrage: Wann was bauen

Entscheidungsregel: Wird diese Zahl innerhalb von 30 Tagen nochmals gefragt?

  • Ja: Es ist ein Dashboard. Die Zahl steht hinter einem Modell. Das Dashboard hat einen Eigentümer.
  • Nein: Senden Sie die CSV. Verlinken Sie das SQL im Anfrage-Thread oder PR. Machen Sie weiter.

Der Fehler besteht darin, für jede Ad-hoc-Anfrage ein Dashboard zu bauen. Sechs Monate später haben Sie 400 Dashboards, 350 davon wurden seit dem Tag ihrer Lieferung nicht mehr geöffnet, und der Analyst, der sie gebaut hat, hat das Unternehmen verlassen.

Dashboards haben ein Eigentümerfeld. Nicht „das Datenteam". Ein Name. Wenn ein Dashboard ohne benannten Eigentümer existiert, archivieren Sie es. Die Aufgabe des Eigentümers ist es, auf „stimmt das noch?" zu antworten, wenn Stakeholder fragen. Wenn niemand es besitzt, kann niemand antworten, und Sie sind zurück beim Vertrauensproblem.

Einmalige SQL-Abfragen kommen in einen analyses/-Ordner in Ihrem dbt-Projekt, versionskontrolliert und reviewed, aber nicht modelliert. Das gibt Ihnen eine Nachverfolgung, ohne das Data Warehouse aufzublähen.

Außerbetriebnahme-Kadenz

Data Warehouses verfallen wie Dachböden: langsam, dann auf einmal. Die Kadenz, die das verhindert:

  • Vierteljährliche Überprüfung von Dashboards nach letztem Aufruf-Datum. Die meisten BI-Tools zeigen das. Ziehen Sie jedes Quartal eine Liste.
  • 60 Tage nicht aufgerufen: Außerbetriebnahme-Banner. Eine sichtbare Meldung „Dieses Dashboard ist für die Archivierung am JJJJ-MM-TT geplant". Benachrichtigt den Eigentümer. Erzwingt eine Entscheidung.
  • 90 Tage nicht aufgerufen: archiviert. In einen archive/-Ordner verschieben. Das zugrundeliegende Modell in der dbt-Konfiguration als enabled: false markieren, damit es nicht mehr gebaut wird.
# models/marts/legacy/_legacy.yml
models:
  - name: fct_orders_2024_legacy
    config:
      enabled: false
    description: |
      Archiviert 2026-04-29, ersetzt durch fct_orders.
      Zuletzt abgefragt 2026-01-12. Eigentümer hat das Unternehmen 2025-Q4 verlassen.

Nach einem Jahr dieser Kadenz werden Sie Ihre Modellanzahl um 30 bis 40 Prozent reduzieren, ohne Downstream-Beschwerden. Die entfernten Modelle waren bereits tot. Sie machen es nur offiziell, damit sie aufhören, Rechenleistung zu kosten und neue Analysten zu verwirren.

Häufige Fallstricke

Die vier, die ich am häufigsten sehe, gerankt nach dem Schaden, den sie anrichten:

  1. Modellieren vor dem Einigen auf Definitionen. Erst das Audit durchführen. Die Definition erhalten. Dann das Modell bauen. Die andere Reihenfolge produziert das Zwei-Umsatz-Problem auf einem frischen Data Warehouse.
  2. Staging überspringen, weil „es nur eine Umbenennung ist". Staging zu überspringen bedeutet, dass jedes Downstream-Modell bricht, wenn sich ein Quell-Schema ändert. Staging ist der Stoßdämpfer. Zahlen Sie die langweilige Steuer.
  3. _v2-Dateien sich multiplizieren lassen. Jedes _v2 ist ein Eingeständnis, dass jemand Angst hatte, das Original zu brechen. Das Original reparieren. Die Änderung kommunizieren. Ein Modell liefern.
  4. Kein Eigentümer auf kritischen Modellen. Ein fct_revenue ohne Eigentümer ist eine Verbindlichkeit mit einer SQL-Datei daran. Das Eigentümerfeld ist in Ihrem Modell-YAML obligatorisch, genauso wie Tests.

Wie „fertig" aussieht

Sie haben diese Disziplin aufgebaut, wenn:

  • Jede Geschäftskennzahl genau ein Modell hat, das sie definiert.
  • Jedes Modell mindestens unique- und not_null-Tests auf seinem Primärschlüssel hat.
  • Jedes Dashboard einen benannten Eigentümer hat.
  • Ein neuer Analyst jede Zahl auf jedem Dashboard in unter zehn Minuten zurück zur Quelle (durch Marts, Intermediate, Staging, Quelle) nachverfolgen kann, ohne jemanden fragen zu müssen.
  • Der CHANGELOG Ihnen sagt, was sich letzte Woche geändert hat, ohne Slack öffnen zu müssen.
  • Ihre Rechenausgaben stagnieren oder sinken, während Ihre Modellanzahl wächst, weil Sie das tote Zeug außer Betrieb genommen haben.

Das ist der Maßstab. Es ist langweilig. Es ist auch die Art, wie Sie aufhören, das Team zu sein, das um 23 Uhr angerufen wird, weil das Board-Deck nicht stimmt.

Mehr erfahren

About the author

Camellia

Camellia

Principal Product Marketing Strategist

Camellia is Principal Product Marketing Strategist at Rework, helping B2B buyers pick the right software with confidence. With 6+ years in product marketing and 150+ SaaS tools evaluated across CRM, project management, and sales engagement, Camellia turns competitive intelligence into clear, honest comparisons. Readers get vendor evaluations they can trust to cut through marketing noise and decide faster.