Type 2 keeps the old row and adds a new row. Each row is one version of the customer. A version is true for a period of time.
Type 2 needs three new columns, and a new surrogate key for each version:
- valid_from: the first day this version is true.
- valid_to: the day this version stops being true. This day is not included.
- is_current: true for the version that is true now.
The not-included rule is a half-open range. The old row ends on 2026-01-08. The new row starts on 2026-01-08.
So every day belongs to exactly one row. There is no gap and no overlap.
A row that is still true gets a far-future end date, 9999-12-31. When Asha moves, you do two steps.
First close the old row. Then add the new row:
Type 2: close the old row, add a new rowworked example
UPDATE dim_customer
SET valid_to = DATE '2026-01-08',
is_current = false
WHERE customer_key = 1001;
INSERT INTO dim_customer
(customer_key, customer_id, name, country,
valid_from, valid_to, is_current)
VALUES
(1006, 1, 'Asha', 'DE',
DATE '2026-01-08', DATE '9999-12-31', true);
dim_customer (after, all 6 rows)
| customer_key | customer_id | name | country | valid_from | valid_to | is_current |
|---|
| 1001 | 1 | Asha | IN | 2026-01-01 | 2026-01-08 | false |
| 1002 | 2 | Ben | GB | 2026-01-01 | 9999-12-31 | true |
| 1003 | 3 | Carla | US | 2026-01-01 | 9999-12-31 | true |
| 1004 | 4 | Dev | NULL | 2026-01-01 | 9999-12-31 | true |
| 1005 | 5 | Omar | IN | 2026-01-01 | 9999-12-31 | true |
| 1006 | 1 | Asha | DE | 2026-01-08 | 9999-12-31 | true |
Asha has two rows. Row 1001 is closed. Row 1006 is the current one. The customer_id is 1 in both.
Now the fact rows. Each order must point at the version that was true on its order date.
Order 101 (2026-01-03) points at 1001. Order 102 (2026-01-10) points at 1006.
fact_orders (Type 2) (6 rows)
| order_id | customer_key | order_date | status | amount |
|---|
| 101 | 1001 | 2026-01-03 | paid | 40.00 |
| 102 | 1006 | 2026-01-10 | paid | 25.00 |
| 103 | 1002 | 2026-01-11 | refunded | 60.00 |
| 104 | 1003 | 2026-01-15 | paid | 15.50 |
| 105 | 1003 | 2026-01-20 | pending | NULL |
| 106 | 1004 | 2026-01-21 | paid | 80.00 |
Run the same revenue query again. The query did not change. Only the keys in the fact table are different:
Type 2: paid revenue by countryworked example
SELECT c.country, SUM(f.amount) AS revenue
FROM fact_orders AS f
JOIN dim_customer AS c ON c.customer_key = f.customer_key
WHERE f.status = 'paid'
GROUP BY c.country
ORDER BY revenue DESC;
revenue by country
| country | revenue |
|---|
| NULL | 80.00 |
| IN | 40.00 |
| DE | 25.00 |
| US | 15.50 |
Order 101 stays under IN (40.00). Order 102 counts under DE (25.00). The 65.00 is split correctly.
Type 2 has a cost. The dimension grows, because each change adds a row.
You also need a way to give each fact row the right key. The next section shows that.