runtime
87 / 88

lesson 87 Warehouse & reporting projects · advanced

Protect fact grain when joining versions

Join historical versions without multiplying fact rows.

the idea

Joining a fact table to a type-2 history must match exactly one version per fact row. Use either the current version (is_current = 1) for “as now” reporting or the version valid on the fact date for “as then” reporting. Both preserve the fact grain; joining to all versions does not.

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 idcustomer_idnumber · links to customerssegmenttextcountrytextvalid_fromdatevalid_todateis_currentnumber
112347occasionalIceland2010-10-312010-11-010
212347regularIceland2010-11-01NULL1
312353occasionalBahrain2010-10-27NULL1
412359occasionalCyprus2009-12-052010-01-010
512359regularCyprus2010-01-012011-11-010
612359key_accountCyprus2011-11-01NULL1
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 idcustomer_idtextcontact_nametextcountrytextregiontext
112347Arthur TurnerIcelandEurope
212353Harry TurnerBahrainRest of World
312359Theo TurnerCyprusEurope
412373Harry HillAustriaEurope
512374Emily HillAustriaEurope
612384Ava MooreSwitzerlandEurope
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 idorder_idnumbercustomer_keynumber · links to dim_customerproduct_keynumber · links to dim_productorder_datedatequantitynumbersalesdecimal
65625367955620622011-01-04834
6562636795568812011-01-0417.95
6562736795568872011-01-0414.95
6562836795564012011-01-0425.9
65629367955640592011-01-043630.6
65630367955643012011-01-041029.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.

  1. See row inflation without a date filter. Joining every history version to every sale multiplies fact_rows—totals would be wrong if you stopped here.the pattern

    example.sql
    SELECT
      COUNT(*) AS fact_rows,
      (
        SELECT COUNT(*)
        FROM fact_sales s
        JOIN dim_customer c ON c.customer_key = s.customer_key
        JOIN customer_history h
          ON h.customer_id = CAST(c.customer_id AS INTEGER)
      ) AS joined_to_all_versions
    FROM fact_sales;
  2. Count rows with current-version join. Filtering to is_current = 1 matches one history row per customer, so the join count stays equal to fact_sales.

    step-2.sql
    SELECT COUNT(*) AS current_join
    FROM fact_sales s
    JOIN dim_customer c ON c.customer_key = s.customer_key
    JOIN customer_history h
      ON h.customer_id = CAST(c.customer_id AS INTEGER)
      AND h.is_current = 1;
  3. Count rows with as-of-date join. Matching valid_from and valid_to to each line’s order_date also preserves one row per sale for historical reporting.

    step-3.sql
    SELECT COUNT(*) AS as_of_join
    FROM fact_sales s
    JOIN dim_customer c ON c.customer_key = s.customer_key
    JOIN customer_history h
      ON h.customer_id = CAST(c.customer_id AS INTEGER)
      AND h.valid_from <= s.order_date
      AND (h.valid_to IS NULL OR s.order_date < h.valid_to);

common mistake

A join on customer only repeats each sale once per version and inflates totals.

challenge 1

Return country and total_sales (rounded to 2) for all 2011 fact_sales lines, using each customer’s current customer_history version.

what to return

Columns
countrytotal_sales in this order
Rows
any order
challenge-1.sql
 
To master this lesson, solve both challenges without hints and pass the concept check. 3 of 3 to go.
Protect fact grain when joining versions — Warehouse & reporting projects · Runtime