lesson 75 Warehouse & reporting projects · advanced
Customer & product reporting contracts
Produce customer and product reports at a defined level of detail.
the idea
A reporting contract fixes the grain, columns and rules of a report so every reader gets the same numbers: one row per customer (including customers with no sales), a defined reference date for recency, and explicit thresholds for segments.
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.
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
dim_product5 columns · 4,902 rows
| product_keynumber · unique id | product_idtext | descriptiontext | categorytext | pricedecimal |
|---|---|---|---|---|
| 1 | 10002 | INFLATABLE POLITICAL GLOBE | Other Gifts | 0.85 |
| 2 | 10002R | ROBOT PENCIL SHARPNER | Stationery & Gift Wrap | 5.17 |
| 3 | 10080 | GROOVY CACTUS INFLATABLE | Other Gifts | 0.39 |
| 4 | 10109 | BENDY COLOUR PENCILS | Stationery & Gift Wrap | 0.42 |
| 5 | 10120 | DOGGY RUBBER | Other Gifts | 0.21 |
| 6 | 10123C | HEARTS WRAPPING TAPE | Stationery & Gift Wrap | 0.65 |
- product_keynumber · unique id
- 1
- product_idtext
- 10002
- descriptiontext
- INFLATABLE POLITICAL GLOBE
- categorytext
- Other Gifts
- pricedecimal
- 0.85
- product_keynumber · unique id
- 2
- product_idtext
- 10002R
- descriptiontext
- ROBOT PENCIL SHARPNER
- categorytext
- Stationery & Gift Wrap
- pricedecimal
- 5.17
- product_keynumber · unique id
- 3
- product_idtext
- 10080
- descriptiontext
- GROOVY CACTUS INFLATABLE
- categorytext
- Other Gifts
- pricedecimal
- 0.39
- product_keynumber · unique id
- 4
- product_idtext
- 10109
- descriptiontext
- BENDY COLOUR PENCILS
- categorytext
- Stationery & Gift Wrap
- pricedecimal
- 0.42
- product_keynumber · unique id
- 5
- product_idtext
- 10120
- descriptiontext
- DOGGY RUBBER
- categorytext
- Other Gifts
- pricedecimal
- 0.21
- product_keynumber · unique id
- 6
- product_idtext
- 10123C
- descriptiontext
- HEARTS WRAPPING TAPE
- categorytext
- Stationery & Gift Wrap
- pricedecimal
- 0.65
first 6 of 4,902 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.
Keep customers with no sales. LEFT JOIN from dim_customer to fact_sales still returns customers with zero lines—an inner join would drop them from a customer report.the pattern
example.sqlSELECT c.customer_id, COUNT(s.sale_key) AS lines FROM dim_customer c LEFT JOIN fact_sales s ON s.customer_key = c.customer_key GROUP BY c.customer_id ORDER BY lines LIMIT 10;
Measure whole days between dates. julianday subtracts two dates and gives a whole-day count—the same idea recency_days uses with a fixed reference date.
step-2.sqlSELECT julianday('2011-12-10') - julianday('2011-11-30') AS days;
common mistake
An inner join from customers to sales silently drops customers with no 2011 purchases; a customer report should keep them with zero sales.