Dimensions and Slowly Changing Dimensions
When a customer moves, history should not move with them
Dimensions describe facts. Slowly Changing Dimensions (SCDs) manage what happens when a description changes: overwrite (type 1), keep history (type 2), or keep only current (type 3).
What you will learn
- Classify dimension changes as type 1, 2, or 3
- Implement a type-2 dimension with surrogate keys and validity dates
- Choose SCD strategy from the business's 'as of' questions
What is this?
A dimension holds descriptive attributes. Slowly changing dimensions handle entity changes over time. Type 1 overwrites (fast, loses history), type 2 creates a new row per version (keeps full history), type 3 keeps one column of 'old' value (limited history).
Why do you need it?
- Analysts constantly ask 'as of then' questions (revenue by customer region, as of that date).
- A wrong SCD choice silently rewrites history and breaks 'prior period' comparisons.
- Type 2 is the workhorse; knowing its costs (share of dimension size, join complexity) matters.
Mental model
Your former address on a fixed paper form
Type 3 (keeping one previous value only)
The form keeps only current + last; older history is gone. Type 2 would keep a folder of every address; type 1 would just erase the old one entirely. Each answers a different 'what did it look like then?' question.
How it works
- A surrogate key identifies the 'customer' entity with versions; each row carries validity dates and a hash of attributes.
- Type 1 updates in place: cheap, but retroactive restatements disappear.
- Type 2 inserts a new version and closes the old (valid_from/valid_to), preserving history while facts point to versions.
- Type 3 tracks one or two prior values in dedicated columns for quick 'previous vs current' comparisons.
- Hashing all attributes compares versions cheaply: identical hash means no change, skip the row.
A type-2 version chain over time.
Examples
Detecting change with a hash, then versioning
-- one-row-per-customer version scan pattern (SQL-ish rules):-- 1. hash = md5(concat(region, tier, ...))-- 2. compare with latest version's hash-- 3. if different: close latest (valid_to = today-1), insert new (valid_from = today) -- Reading as-of:SELECT sum(f.gross_amount)FROM fact_sales fJOIN dim_customer d ON f.customer_key = d.customer_keyWHERE '2026-02-15' BETWEEN d.valid_from AND d.valid_to AND d.region = 'West';Validity dates let any fact row resolve exactly which version described it then.
In production
- A sales dashboard hides 'as of quarter' figures by joining facts to the dimension version valid that day.
- A compliance team wants change audit for addresses: type 2 preserves the trail.
- A fast-moving campaign dimension uses type 1 for low-stakes attributes and reserves type 2 for tracked ones.
Best practices
- Pick type 1/2/3 per attribute or per requirement, not wholesale.
- Hash every attribute to detect changes cheaply instead of comparing column by column.
- Include dbt-style snapshot columns (dbt_valid_from/to) and test them in the model.
- Bound type-2 dimension growth: one version per change event, not per row touched.
Common mistakes
- Applying type 2 to every column, doubling dimension size for attributes nobody queries historically.
- Using valid_to = 9999-12-31 everywhere without handling open-ended joins.
- Overwriting a type-1 revision and making 'this period vs last year' comparisons impossible.
Debugging tips
- When as-of reports show missing rows, check the version with valid_to before the query date.
- A type-2 dimension exploding in size usually indicates a churning attribute (e.g. last_updated) folded into the hash.
- Verify your hash excluding volatile columns before trusting the 'no change' short-circuit.
Interview checkpoint
Hands-on lab
- Track source customer updates with an attribute hash
- Close the current version and open a new one only on real changes
- Answer an as-of question against versioned rows
Implement a Type-2 Dimension with Hash Detection
40 min · intermediate
Quick quiz
Which SCD strategy preserves full history by creating a new row per change?
A growing type-2 dimension is a perfectly normal, expected behavior for history-preserving attributes.