lesson 84 Warehouse & reporting projects · advanced
Latest-state reporting versus SCD Type 2
Compare current-state reporting with preserved history.
the idea
A latest-state table keeps only the current value of each attribute; a type-2 history keeps every version with a validity interval. Reports about “now” use the current version; reports about the past join each fact to the version valid on its date.
Dataset: Retail
The tables the challenges use, with their first 6 rows. Under each column name: the kind of value it holds, and which table it links to.
customer_history7 columns · 1,685 rows
| history_idnumber · unique id | customer_idnumber · links to customers | segmenttext | countrytext | valid_fromdate | valid_todate | is_currentnumber |
|---|---|---|---|---|---|---|
| 1 | 12347 | occasional | Iceland | 2010-10-31 | 2010-11-01 | 0 |
| 2 | 12347 | regular | Iceland | 2010-11-01 | NULL | 1 |
| 3 | 12353 | occasional | Bahrain | 2010-10-27 | NULL | 1 |
| 4 | 12359 | occasional | Cyprus | 2009-12-05 | 2010-01-01 | 0 |
| 5 | 12359 | regular | Cyprus | 2010-01-01 | 2011-11-01 | 0 |
| 6 | 12359 | key_account | Cyprus | 2011-11-01 | NULL | 1 |
- history_idnumber · unique id
- 1
- customer_idnumber · links to customers
- 12347
- segmenttext
- occasional
- countrytext
- Iceland
- valid_fromdate
- 2010-10-31
- valid_todate
- 2010-11-01
- is_currentnumber
- 0
- history_idnumber · unique id
- 2
- customer_idnumber · links to customers
- 12347
- segmenttext
- regular
- countrytext
- Iceland
- valid_fromdate
- 2010-11-01
- valid_todate
- NULL
- is_currentnumber
- 1
- history_idnumber · unique id
- 3
- customer_idnumber · links to customers
- 12353
- segmenttext
- occasional
- countrytext
- Bahrain
- valid_fromdate
- 2010-10-27
- valid_todate
- NULL
- is_currentnumber
- 1
- history_idnumber · unique id
- 4
- customer_idnumber · links to customers
- 12359
- segmenttext
- occasional
- countrytext
- Cyprus
- valid_fromdate
- 2009-12-05
- valid_todate
- 2010-01-01
- is_currentnumber
- 0
- history_idnumber · unique id
- 5
- customer_idnumber · links to customers
- 12359
- segmenttext
- regular
- countrytext
- Cyprus
- valid_fromdate
- 2010-01-01
- valid_todate
- 2011-11-01
- is_currentnumber
- 0
- history_idnumber · unique id
- 6
- customer_idnumber · links to customers
- 12359
- segmenttext
- key_account
- countrytext
- Cyprus
- valid_fromdate
- 2011-11-01
- valid_todate
- NULL
- is_currentnumber
- 1
first 6 of 1,685 rows · your queries use all of them
dim_customer5 columns · 848 rows
| customer_keynumber · unique id | customer_idtext | contact_nametext | countrytext | regiontext |
|---|---|---|---|---|
| 1 | 12347 | Arthur Turner | Iceland | Europe |
| 2 | 12353 | Harry Turner | Bahrain | Rest of World |
| 3 | 12359 | Theo Turner | Cyprus | Europe |
| 4 | 12373 | Harry Hill | Austria | Europe |
| 5 | 12374 | Emily Hill | Austria | Europe |
| 6 | 12384 | Ava Moore | Switzerland | Europe |
- customer_keynumber · unique id
- 1
- customer_idtext
- 12347
- contact_nametext
- Arthur Turner
- countrytext
- Iceland
- regiontext
- Europe
- customer_keynumber · unique id
- 2
- customer_idtext
- 12353
- contact_nametext
- Harry Turner
- countrytext
- Bahrain
- regiontext
- Rest of World
- customer_keynumber · unique id
- 3
- customer_idtext
- 12359
- contact_nametext
- Theo Turner
- countrytext
- Cyprus
- regiontext
- Europe
- customer_keynumber · unique id
- 4
- customer_idtext
- 12373
- contact_nametext
- Harry Hill
- countrytext
- Austria
- regiontext
- Europe
- customer_keynumber · unique id
- 5
- customer_idtext
- 12374
- contact_nametext
- Emily Hill
- countrytext
- Austria
- regiontext
- Europe
- customer_keynumber · unique id
- 6
- customer_idtext
- 12384
- contact_nametext
- Ava Moore
- countrytext
- Switzerland
- regiontext
- Europe
first 6 of 848 rows · your queries use all of them
fact_sales7 columns · 45,151 rows
| sale_keynumber · unique id | order_idnumber | customer_keynumber · links to dim_customer | product_keynumber · links to dim_product | order_datedate | quantitynumber | salesdecimal |
|---|---|---|---|---|---|---|
| 65625 | 3679 | 556 | 2062 | 2011-01-04 | 8 | 34 |
| 65626 | 3679 | 556 | 881 | 2011-01-04 | 1 | 7.95 |
| 65627 | 3679 | 556 | 887 | 2011-01-04 | 1 | 4.95 |
| 65628 | 3679 | 556 | 401 | 2011-01-04 | 2 | 5.9 |
| 65629 | 3679 | 556 | 4059 | 2011-01-04 | 36 | 30.6 |
| 65630 | 3679 | 556 | 4301 | 2011-01-04 | 10 | 29.5 |
- sale_keynumber · unique id
- 65625
- order_idnumber
- 3679
- customer_keynumber · links to dim_customer
- 556
- product_keynumber · links to dim_product
- 2062
- order_datedate
- 2011-01-04
- quantitynumber
- 8
- salesdecimal
- 34
- sale_keynumber · unique id
- 65626
- order_idnumber
- 3679
- customer_keynumber · links to dim_customer
- 556
- product_keynumber · links to dim_product
- 881
- order_datedate
- 2011-01-04
- quantitynumber
- 1
- salesdecimal
- 7.95
- sale_keynumber · unique id
- 65627
- order_idnumber
- 3679
- customer_keynumber · links to dim_customer
- 556
- product_keynumber · links to dim_product
- 887
- order_datedate
- 2011-01-04
- quantitynumber
- 1
- salesdecimal
- 4.95
- sale_keynumber · unique id
- 65628
- order_idnumber
- 3679
- customer_keynumber · links to dim_customer
- 556
- product_keynumber · links to dim_product
- 401
- order_datedate
- 2011-01-04
- quantitynumber
- 2
- salesdecimal
- 5.9
- sale_keynumber · unique id
- 65629
- order_idnumber
- 3679
- customer_keynumber · links to dim_customer
- 556
- product_keynumber · links to dim_product
- 4059
- order_datedate
- 2011-01-04
- quantitynumber
- 36
- salesdecimal
- 30.6
- sale_keynumber · unique id
- 65630
- order_idnumber
- 3679
- customer_keynumber · links to dim_customer
- 556
- product_keynumber · links to dim_product
- 4301
- order_datedate
- 2011-01-04
- quantitynumber
- 10
- salesdecimal
- 29.5
first 6 of 45,151 rows · your queries use all of them
worked example
Run these steps in order to see the idea before you write your own query. The step marked “the pattern” is what the challenges ask for. Running the example does not count toward progress.
Walk one customer’s history intervals. Each row is a type-2 version with valid_from, valid_to, and is_current—half-open intervals avoid double-counting boundary days.the pattern
example.sqlSELECT customer_id, segment, country, valid_from, valid_to, is_current FROM customer_history WHERE customer_id = 12359 ORDER BY valid_from;
See how many versions customers have. The distribution shows how often segment or country changed—historical reports must join facts to the right version, not every row.
step-2.sqlSELECT versions, COUNT(*) AS customers FROM ( SELECT customer_id, COUNT(*) AS versions FROM customer_history GROUP BY customer_id ) GROUP BY versions;
Pick the version valid on a date. For 2011-06-01 on customer 12359, valid_from <= date and date < valid_to (or NULL end) returns exactly one segment.
step-3.sqlSELECT customer_id, segment, valid_from, valid_to FROM customer_history WHERE customer_id = 12359 AND valid_from <= '2011-06-01' AND (valid_to IS NULL OR '2011-06-01' < valid_to);
common mistake
Joining facts to every history version without a date condition repeats each sale once per version.