Biblioteca de conceptos

Slowly Changing Dimensions

A set of patterns (Type 0 through Type 7) for handling attribute changes in dimension tables — choosing how much history to preserve and how to expose it.

What it is

SCD is the modeling vocabulary for answering one question: when an attribute of a dimension changes, what should the warehouse remember? The answer determines whether historical reports stay correct or silently rewrite themselves.

The seven canonical types:

  • Type 0 — never change. The original value is the truth.
  • Type 1 — overwrite. Keep only the current value. History is lost.
  • Type 2 — add a new row with effective-from / effective-to dates. Full history preserved.
  • Type 3 — add a column for the previous value. Limited history.
  • Type 4 — split current and historical into separate tables.
  • Type 6 — combination of 1, 2, and 3 (the “hybrid”).
  • Type 7 — dual surrogate keys for current and historical use.

In practice, almost every implementation uses some mix of Type 1 and Type 2.

Why it matters

The wrong SCD choice silently corrupts historical analysis. If a customer’s region changes from EMEA to APAC and you’re using Type 1, every old order suddenly attributes to APAC — and the EMEA Q3 report shrinks for no apparent reason. Auditors notice.

The right choice depends on the consuming question. If you always want “as of today’s truth,” Type 1 is fine. If you need “as of the time of the event,” you need Type 2.

How it works

Type 2 is the workhorse. The pattern:

| customer_sk | customer_id | region | valid_from   | valid_to     | is_current |
|-------------|-------------|--------|--------------|--------------|------------|
| 1           | C-100       | EMEA   | 2023-01-01   | 2024-06-30   | false      |
| 2           | C-100       | APAC   | 2024-07-01   | 9999-12-31   | true       |

Fact tables join on the surrogate key (customer_sk), which means each fact row is bound to the dimension state at the time of the event.

Vendor comparison

Approachdbt snapshotsDelta Lake MERGE / Snowflake MERGECustom SQL
FormDeclarative dbt featureImperative DMLHand-rolled
Best forMost modern stacksEngine-native controlEdge cases

dbt snapshots. The default. Handles Type 2 with a few lines of YAML. Integrates cleanly with the rest of dbt.

Native MERGE. When you need fine control over performance — e.g., very large dimensions where dbt’s default approach is too slow — falling to native MERGE statements in Snowflake or Databricks is appropriate.

Yoann’s take

I default to Type 2 for any dimension where historical fidelity might matter, Type 1 for everything else, and explicit Type 4 only when the historical table volume is large enough that splitting it from the current table genuinely saves on query performance. The seven-type taxonomy is good vocabulary; in practice you live in two of them.

— Yoann