lesson 76 Warehouse & reporting projects · advanced
Monthly change & cumulative reports
Measure monthly change and cumulative totals.
the idea
Monthly reports aggregate to one row per month first, then use window functions across months: SUM(…) OVER (ORDER BY month) for cumulative sales and LAG(…) for the previous month’s value and the percentage change.
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.
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.
Aggregate sales by month first. One row per month with rounded totals—that monthly grain is what running sums and month-over-month windows need.the pattern
example.sqlSELECT substr(order_date, 1, 7) AS month, ROUND(SUM(sales), 2) AS sales FROM fact_sales GROUP BY month ORDER BY month;
Find the busiest months by orders. Distinct order_id per month shows which months drove the most orders—November usually leads for this wholesaler.
step-2.sqlSELECT substr(order_date, 1, 7) AS month, COUNT(DISTINCT order_id) AS orders FROM fact_sales GROUP BY month ORDER BY orders DESC LIMIT 3;
Compare each month to the prior one. After aggregating to months, LAG(orders) pulls the previous month’s value on the same result set.
step-3.sqlWITH m AS ( SELECT substr(order_date, 1, 7) AS month, COUNT(DISTINCT order_id) AS orders FROM fact_sales GROUP BY month ) SELECT month, orders, LAG(orders) OVER (ORDER BY month) AS previous_orders FROM m ORDER BY month;
common mistake
Applying LAG before aggregating compares sales lines, not months.