Slowly Changing Dimensions
How to handle dimension attributes that change over time in a Power BI model, using Type 1 and Type 2 slowly changing dimension patterns.
Slowly Changing Dimensions
A slowly changing dimension (SCD) is a dimension whose attributes change occasionally rather than never — a customer moves region, a product gets reassigned to a new category, an employee changes department.
DimCustomer
CustomerKey | CustomerName | Region
1001 | Alice Smith | WestThe question an SCD strategy answers: when that row changes, should the history behind it change too?
Type 0: Fixed, No Changes
The attribute never changes after the row is created — a date of birth, an original signup date.
No updates. The value written once stays as-is forever.No special handling is needed; this is the default for anything that genuinely can't change.
Type 1: Overwrite
The old value is simply replaced with the new one, and history is not preserved.
Before: After Alice moves to East:
CustomerKey | Region CustomerKey | Region
1001 | West 1001 | EastEvery past and future report using this dimension now shows Alice in the East, even for sales that happened while she was in the West.
FactSales (unchanged)
|
| joins to
|
DimCustomer[CustomerKey] = 1001, Region = East (always, even for old sales)Type 1 is simple and appropriate when historical accuracy for that specific attribute doesn't matter — correcting a misspelled name, for example.
Type 2: Preserve History with New Rows
Instead of overwriting the row, a new row is added, and the old row is marked as no longer current.
CustomerKey | CustomerName | Region | EffectiveDate | ExpiryDate | IsCurrent
1001 | Alice Smith | West | 2020-01-01 | 2024-06-01 | No
1002 | Alice Smith | East | 2024-06-01 | (null) | YesTwo structural changes make this work:
- A surrogate key (
CustomerKey) that's independent of the business key (Alice's real-world identity), so the same person can have multiple dimension rows. - Effective/expiry dates (or an
IsCurrentflag) marking which row was valid at any point in time.
FactSales (DateKey, CustomerKey)
|
| each historical sale references
|
the CustomerKey that was current on the sale's dateA sale made in 2022 references CustomerKey = 1001 (West), and a sale made in 2025 references CustomerKey = 1002 (East) — history stays accurate to how things were at the time.
Type 3: Track Limited History in Columns
Rather than adding rows, a Type 3 dimension adds a column to hold the previous value.
CustomerKey | Region | PreviousRegion
1001 | East | WestThis only tracks one prior state, and is used far less often than Type 1 or Type 2 — mostly when a single "what was it before" comparison is enough, and full history isn't needed.
Choosing a Type
Does the attribute's history matter for reporting?
|
+-- No -> Type 1 (overwrite)
|
+-- Yes -> Type 2 (new row per change)
|
+-- Only need the immediately prior value? -> Type 3Most enterprise models use Type 1 for corrections and low-impact attributes, and Type 2 for anything that affects how past transactions should be grouped or filtered — like sales territory or customer segment.
Implementing Type 2 in Power Query
Detecting a changed row typically means comparing the incoming source row against the current dimension row, and inserting a new row — with a new surrogate key and updated effective date — whenever a tracked attribute differs. This logic usually lives in the ETL/Power Query layer, not in DAX. DAX consumes the resulting historized table; it doesn't create the history.
Best Practices
- Use a surrogate key, not the business key, whenever a dimension needs Type 2 history — the business key alone can't represent "this customer, but as they were before."
- Only apply Type 2 to attributes where historical accuracy actually matters for reporting; applying it to every attribute bloats the dimension table with rows that don't add analytical value.
- Store effective and expiry dates (or a clear
IsCurrentflag) so "what was true on this date" can always be answered. - Keep the fact table's foreign key pointing at the surrogate key that was current when the transaction happened, not the current row.
Common Mistakes
Using Type 1 When History Matters
Overwriting a region or segment attribute that reports are sliced by silently rewrites history — a customer who moved regions last month now appears to have always been in the new region.
Using the Business Key Instead of a Surrogate Key
Without a surrogate key, there's no way to have two rows represent "the same customer, at two different points in time."
Applying Type 2 to Everything
Tracking full history for attributes nobody analyzes historically, like a phone number, just grows the dimension table and adds unnecessary complexity.
Slowly Changing Dimension Checklist
- Each attribute has a deliberate choice of Type 0, 1, 2, or 3 — not a default applied blindly.
- Type 2 dimensions use a surrogate key, separate from the business key.
- Type 2 dimensions store effective/expiry dates or an
IsCurrentflag. - Fact tables reference the surrogate key that was current at the time of the transaction.
Next Steps
Continue learning Power BI data modeling: