Week 2 · Bonus — optional extra studio
Bonus studio 2: SQL cohort and window analytics on the airline sample database
- 90 minsession length
- 5timed blocks
- 4tasks
- —status
Session plan90 min
Before you start
Prepare before the session (under 2 minutes).
- The sample database
python/artifacts/airline_sample.sqlite(seed 42, synthetic; rebuild withcd python && uv run python -m capstone.airline_simif missing). - The
sqlite3command-line shell (version 3.25 or later for window functions; version 3.28 or later forRANGEwith a numeric offset). - A folder
forecasting-project/sql/for the files.
Tables used: flight_day (flight number x departure date), booking_curve (flight number x departure date x
snapshot; snapshots at 90, 60, 45, 30, 21, 14, 10, 7, 5, 3, 2, 1 and 0 days out), flight_day_raw (the messy copy
for 2022-2023 from Day 6). ROW_NUMBER, RANK, DENSE_RANK, LAG, AVG ... OVER and conditional aggregation are all
allowed.
This is analytics, not modelling: the queries read departures through 2023-12-31 and no test-period row.
Blocks5
Block 1: Plan the queries and their control totals
10 minBefore writing SQL, do the first half of task
reconcile-and-record: write down, for each of the three build tasks, the grain of the result, the number of rows you expect and one control total it must reconcile to. This takes 10 minutes. Run your SQL withsqlite3 -header -column python/artifacts/airline_sample.sqlite < sql/<file>.sql.Block 2: Build 1 - departure-month and market cohorts of booking pace
25 minWork on task
cohort-pace.- Minutes 0-5 - the fan-out (step 1): see the wrong number first.
- Minutes 5-18 - the cohort matrix (steps 2 and 3).
- Minutes 18-25 - the second cut and the reading (steps 4 and 5).
Block 3: Build 2 - rolling windows and year-over-year on a clean and a gappy table
25 minWork on task
rolling-and-yoy.- Minutes 0-12 - the clean table (steps 1 to 3).
- Minutes 12-25 - the gappy table (steps 4 and 5).
Block 4: Build 3 - top-N, ties and share of total
20 minWork on task
topn-ties-share.- Minutes 0-12 - top five per route under three ranking functions (steps 1 and 2).
- Minutes 12-20 - share of total (steps 3 and 4).
Block 5: Reconcile and record
10 minFinish task
reconcile-and-record: fill in observed values against the control totals you wrote in the first block, and write the three guards you will reuse.
Tasks4
Task 1
codeA booking-pace cohort groups departures and asks what share of their final bookings had been sold at a given number of days out. Use departures in calendar year 2023.
- Join
flight_daytobooking_curveon(date, flight_no)with no snapshot filter. Report the row count andSUM(bookings_final)against the same figures fromflight_dayalone. Explain the ratio in one sentence. - Build the matrix with one row per departure month and one column for each of D-60, D-30, D-14 and D-7. Each
cell is the sum of
cumulative_bookingsat that snapshot divided by the sum at D-0, over all departures in the month. Write one query using conditional aggregation. - Check the D-0 sum of each month against
SUM(bookings_final)for the same month inflight_day. - Cut the same share at D-30 by
marketfor the whole year. - Write three sentences: how much do the month cohorts differ, how much do the market cohorts differ, and what that implies for using the month as a segmentation of booking pace.
- Join
Task 2
codePart A uses the complete table
flight_day; part B uses the messyflight_day_raw.- For flight
ZX118, for departures 2022-01-01 to 2023-12-31, computepassengersand a 7-day rolling mean (the current day and the 6 before it) withAVG(...) OVER (ORDER BY date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW). - Make the rolling mean
NULLwhile the window holds fewer than 7 rows. Report how many rows that affects. - Add the value from the same weekday one year earlier with
LAG(passengers, 364)and the percentage change. Report passengers, the rolling mean, the lagged value and the lag date for 2023-07-15 and 2023-12-31. Say why 364 and not 365. - For flight
ZX109inflight_day_raw, departures in 2023, remove duplicate rows first (one row per date), then compute the same rolling mean two ways: withROWS BETWEEN 6 PRECEDINGand with a calendar windowORDER BY julianday(date) RANGE BETWEEN 6 PRECEDING AND CURRENT ROW. Report both values on 2023-03-13 and 2023-03-14, the first date of the 7 rows theROWSwindow used on 2023-03-13, and the number of days present in the calendar window. - Write the guard you will add to any rolling or lag calculation on a feed that may have gaps.
- For flight
Task 3
codeUse
flight_day, departures in 2023.- For each route, return the 5 departures with the most
bookings_final, usingROW_NUMBERwith a tie-break (date, thenflight_no). Report the total row count. - Repeat with
RANK()and withDENSE_RANK()and keep rows with rank at most 5. Report the total row count for each and, for each route, how many rows sit at the booking cap (bookings_final = CAST(capacity * 1.06 AS INT)). - For December 2023, return each route's
SUM(bookings_final)and its percentage of the network total using a window over the grouped result (no subquery for the total). Add the rank of the route. - Repeat step 3 from the join of
flight_daytobooking_curvewith no snapshot filter. State what changed and what did not.
- For each route, return the 5 departures with the most
Task 4
reviewIn the first block, before running anything, write a table with one row per build task: the grain of the result, the number of rows you expect, and one control total or invariant it must reconcile to (for example, which table's sum, and for which period).
After the builds, add the observed values and the difference. Then write three guards, each as a SQL query that returns zero rows when everything is fine, that you will reuse:
- a grain guard for a join (the joined row count equals the left table's count);
- a window guard (every windowed value has the minimum number of days behind it);
- a tie guard (a top-N query returns exactly N rows per group, or the group is reported).
State which of your three builds you would trust least and why.
Rubric3 criteria
| Criterion | Meets | Exceeds |
|---|---|---|
| Grain and reconciliation | Every query states its grain and reconciles to a control total taken from another query or table. The fan-out is reproduced and explained. | The reconciliations are written before the queries run, and a guard demonstrably fails on the defect it targets (fan-out, gap or tie). |
| Window correctness | Frames are explicit (`ROWS` or `RANGE`), partial windows are handled, and the year-over-year lag is justified by the weekday. | The learner shows where `ROWS` and calendar frames disagree on a gappy table and adds a minimum-count guard. |
| Ranking and interpretation | Tie behaviour of the three ranking functions is demonstrated with counts, and the learner explains the cap. | The learner replaces a misleading top-N with the question the business is asking, and reports shares with the caveat that uniform fan-outs do not change them. |
Required output
Saved in forecasting-project/sql/ and notes/:
bonus02_cohort_pace.sql,bonus02_rolling_yoy.sql,bonus02_topn_share.sql;- the reconciliation table (expected against observed);
- the three guard queries, each shown failing on its target defect;
- a short note on which query you trust least.
The studio is optional and changes no completion count.
Studios are tracked separately from the 60 core lessons; completing one never completes a lesson.