From c1cb63c7d9c2d79777aa7c669c2017c17819a4c9 Mon Sep 17 00:00:00 2001 From: Lasse Benninga Date: Thu, 25 Jun 2026 10:31:39 +0200 Subject: [PATCH 1/2] fix(data-track/week-9): clarify validation expectations, deterministic sample, assignment cast note Polish from a Week 9 exercise review (all solutions live-verified against the shared Azure Postgres: Manhattan top, 0 NULL pickups, 189 duplicate groups, 0 orphans, fact view 56,369 = 56,551 - 182 negatives). - Ex1: add ORDER BY pickup_datetime before LIMIT 5 so the sample is stable between runs (a bare LIMIT returns an arbitrary 5 rows). - Ex5: tell students 5a returns 0 and 5c returns empty BY DESIGN (pickup IDs are complete; orphans do not exist on this load). A 0 is the check passing, not a mistake. Prevents the "did I break it?" confusion. - Ex6: note the practice view is scaled down; the assignment's vw_fact_trips also casts pickup_datetime::TIMESTAMP. Flag it so students do not under-build the assignment deliverable. Co-Authored-By: Claude Opus 4.8 (1M context) --- data-track/week-9/exercise_1/exercise.sql | 4 +++- data-track/week-9/exercise_1/solutions/exercise.sql | 1 + data-track/week-9/exercise_5/README.md | 2 +- data-track/week-9/exercise_5/exercise.sql | 2 ++ data-track/week-9/exercise_6/README.md | 4 +++- data-track/week-9/exercise_6/exercise.sql | 3 +++ 6 files changed, 13 insertions(+), 3 deletions(-) diff --git a/data-track/week-9/exercise_1/exercise.sql b/data-track/week-9/exercise_1/exercise.sql index 2d8e15b..d6bd727 100644 --- a/data-track/week-9/exercise_1/exercise.sql +++ b/data-track/week-9/exercise_1/exercise.sql @@ -10,6 +10,8 @@ -- Run this against your OWN schema on the shared Azure PostgreSQL, not public. -- -- Hint: The bridge between the two tables is t.pickup_location_id = z.location_id. --- Use an INNER JOIN and LIMIT 5. +-- Use an INNER JOIN. Add ORDER BY t.pickup_datetime before LIMIT 5 so the +-- five rows are the same every run (a bare LIMIT returns an arbitrary +-- sample that can change between runs). -- TODO: select the four columns and join nyc_taxi.raw_trips to nyc_taxi.raw_zones, then limit to 5 rows. diff --git a/data-track/week-9/exercise_1/solutions/exercise.sql b/data-track/week-9/exercise_1/solutions/exercise.sql index 7f077e5..16a6864 100644 --- a/data-track/week-9/exercise_1/solutions/exercise.sql +++ b/data-track/week-9/exercise_1/solutions/exercise.sql @@ -8,4 +8,5 @@ SELECT FROM nyc_taxi.raw_trips t INNER JOIN nyc_taxi.raw_zones z -- WHY INNER JOIN: every trip we keep must have a matching zone; trips with no match are dropped, which is fine here ON t.pickup_location_id = z.location_id -- WHY this ON: the numeric pickup_location_id in the fact table maps to location_id in the dimension +ORDER BY t.pickup_datetime -- WHY ORDER BY: without it, LIMIT returns an arbitrary 5 rows that can differ between runs; ordering makes the sample stable and reproducible LIMIT 5; -- WHY LIMIT 5: we only need a small sample to confirm the join reads correctly, not all 57K rows diff --git a/data-track/week-9/exercise_5/README.md b/data-track/week-9/exercise_5/README.md index 468d91f..c746413 100644 --- a/data-track/week-9/exercise_5/README.md +++ b/data-track/week-9/exercise_5/README.md @@ -14,6 +14,6 @@ Open `exercise.sql` and run each of the three queries against your own schema on ## Success criteria -Each check returns a clear answer. For 5a you get a single count. For 5b and 5c, an empty result means the check passed: any rows returned are the problems to report. +Each check returns a clear answer. For 5a you get a single count, and on this dataset that count is **0**: the pickup location IDs are complete. That is the point of the check, not a sign you wrote it wrong. The real issues live elsewhere: 5b surfaces duplicate trips, and the negative-fare and NULL `payment_type` problems show up in the chapter. For 5b and 5c, an empty result means the check passed: any rows returned are the problems to report. 5c also comes back empty here (every pickup ID resolves to a zone), which is the correct passing outcome, not a missed bug. Stuck? The reference queries are in `solutions/exercise.sql`, try for 10 to 20 minutes first. diff --git a/data-track/week-9/exercise_5/exercise.sql b/data-track/week-9/exercise_5/exercise.sql index 1568323..627e10b 100644 --- a/data-track/week-9/exercise_5/exercise.sql +++ b/data-track/week-9/exercise_5/exercise.sql @@ -17,6 +17,8 @@ -- 5a. Trips with a missing pickup location -- TODO: count rows where pickup_location_id IS NULL. +-- Expect 0 here: the pickup IDs are complete. A 0 is the check passing, +-- not a mistake. The real dirt is duplicates (5b) and negative fares. -- 5b. Duplicate trips (same vendor + pickup + dropoff time) diff --git a/data-track/week-9/exercise_6/README.md b/data-track/week-9/exercise_6/README.md index 5b6330c..ce1f8ab 100644 --- a/data-track/week-9/exercise_6/README.md +++ b/data-track/week-9/exercise_6/README.md @@ -14,6 +14,8 @@ Open `exercise.sql` and run it against your own schema on the shared Azure Postg ## Success criteria -After 6a, both views exist in your schema and can be queried. 6b returns the single highest-revenue borough, and 6c returns five pickup zones ranked by trip count. +After 6a, both views exist in your schema and can be queried. 6b returns the single highest-revenue borough (Manhattan), and 6c returns five pickup zones ranked by trip count. + +> This practice view is deliberately scaled down. The assignment's `vw_fact_trips` also casts `pickup_datetime` to a `TIMESTAMP` (`pickup_datetime::TIMESTAMP`). Add that cast when you build the assignment version, or your fact view will not match the deliverable. Stuck? The reference queries are in `solutions/exercise.sql`, try for 10 to 20 minutes first. diff --git a/data-track/week-9/exercise_6/exercise.sql b/data-track/week-9/exercise_6/exercise.sql index 8ca34f0..688410e 100644 --- a/data-track/week-9/exercise_6/exercise.sql +++ b/data-track/week-9/exercise_6/exercise.sql @@ -10,6 +10,9 @@ -- Dataset: nyc_taxi.raw_trips (~57K green-taxi rows, Jan 2024) and nyc_taxi.raw_zones (265 rows). -- Run these against your OWN schema on the shared Azure PostgreSQL, not public. -- NOTE: 6a creates views in YOUR OWN schema. CREATE OR REPLACE VIEW is safe to re-run. +-- NOTE: this practice view is scaled down. The Week 9 assignment's vw_fact_trips +-- also casts pickup_datetime to a TIMESTAMP (pickup_datetime::TIMESTAMP). +-- Add that cast when you build the assignment version. -- -- Hint: A view is a saved query: CREATE VIEW name AS SELECT ... The borough and -- zone names live in vw_dim_zones, so join From 872a63cd144b0150ce70f78a54219c83aacc09bf Mon Sep 17 00:00:00 2001 From: Lasse Benninga Date: Sun, 28 Jun 2026 21:50:17 +0200 Subject: [PATCH 2/2] Fix double precision to numeric type casting issue for ROUND() in week 9 exercise solutions --- data-track/week-9/exercise_2/solutions/exercise.sql | 2 +- data-track/week-9/exercise_4/solutions/exercise.sql | 2 +- data-track/week-9/exercise_6/solutions/exercise.sql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data-track/week-9/exercise_2/solutions/exercise.sql b/data-track/week-9/exercise_2/solutions/exercise.sql index bb7e129..c376c3e 100644 --- a/data-track/week-9/exercise_2/solutions/exercise.sql +++ b/data-track/week-9/exercise_2/solutions/exercise.sql @@ -3,7 +3,7 @@ SELECT z.borough, COUNT(*) AS total_trips, -- WHY COUNT(*): counts every row in the group, which is one row per trip, so this is the trip count per borough - ROUND(AVG(t.fare_amount), 2) AS avg_fare -- WHY ROUND: AVG returns many decimal places; rounding to 2 keeps the fare readable as currency + ROUND(AVG(t.fare_amount)::numeric, 2) AS avg_fare -- WHY ROUND: AVG returns many decimal places; rounding to 2 keeps the fare readable as currency FROM nyc_taxi.raw_trips t INNER JOIN nyc_taxi.raw_zones z ON t.pickup_location_id = z.location_id diff --git a/data-track/week-9/exercise_4/solutions/exercise.sql b/data-track/week-9/exercise_4/solutions/exercise.sql index 6b66c9a..8586387 100644 --- a/data-track/week-9/exercise_4/solutions/exercise.sql +++ b/data-track/week-9/exercise_4/solutions/exercise.sql @@ -15,7 +15,7 @@ joined AS ( -- WHY second CTE: names the "join" step; it read ) SELECT borough, - ROUND(AVG(fare_amount), 2) AS avg_fare -- WHY final SELECT only aggregates: the filtering and joining are already done, so this step does one thing + ROUND(AVG(fare_amount)::numeric, 2) AS avg_fare -- WHY final SELECT only aggregates: the filtering and joining are already done, so this step does one thing FROM joined GROUP BY borough ORDER BY avg_fare DESC; diff --git a/data-track/week-9/exercise_6/solutions/exercise.sql b/data-track/week-9/exercise_6/solutions/exercise.sql index 0da1e41..ea6f9ab 100644 --- a/data-track/week-9/exercise_6/solutions/exercise.sql +++ b/data-track/week-9/exercise_6/solutions/exercise.sql @@ -17,7 +17,7 @@ WHERE fare_amount >= 0; -- WHY filter in the view: the cleaning rule (no n -- 6b. Highest total fare revenue by borough SELECT d.borough, - ROUND(SUM(f.fare_amount), 2) AS total_revenue -- WHY SUM: revenue is the total of all fares in the borough, not an average + ROUND(SUM(f.fare_amount)::numeric, 2) AS total_revenue -- WHY SUM: revenue is the total of all fares in the borough, not an average FROM vw_fact_trips f INNER JOIN vw_dim_zones d -- WHY join to the dim view: borough names live in the dimension, not the fact, so we join to get the breakdown label ON f.pickup_location_id = d.location_id