Unit 4.4 · about 6 h
22 / 324Relational modeling, joins, aggregation and window functions
ML features and labels come from joins and aggregations. A misplaced WHERE turns an outer join inner, and a GROUP BY at the wrong grain duplicates or drops rows. Predicting counts before running is the skill that prevents silent training-data corruption.
- implement
You can now
- Design a normalized schema with primary, foreign,
uniqueandcheckconstraints, and state which anomaly each normalization move fixes. - Predict row counts and duplicate behavior of multi-join queries before executing, including
LEFT JOINwithWHEREand junction fan-out. - Write aggregation with
HAVING, CTEs and window functions, and distinguish window row identity fromGROUP BYcollapsing.
Covers
- Tables, keys, constraints and normalized relationships
- Joins, aggregation, CTEs and window functions
Study, in this order
10 steps
- The Relational ModelAlgoMaster SQL - selected route
NULL: The Missing Value Problem (NULLComparisons, Three-Valued Logic,NULLin Practice); Relationships Between Tables (one-to-many, many-to-many, self-referencing) - Keys and ConstraintsAlgoMaster SQL - selected route
Primary Keys; Natural vs Surrogate Keys; Foreign Keys with
ON DELETE; Composite Keys; Column Constraints (UNIQUE,NOT NULL,CHECK,DEFAULT) - NormalizationAlgoMaster SQL - selected route
The Problem: Data Anomalies (update, insert, delete); First, Second and Third Normal Form with The Practical Test for 3NF; Boyce-Codd Normal Form; The Quick Test
- Entity-Relationship ModelingAlgoMaster SQL - selected route
Cardinality: Many-to-Many (Extra Attributes on Junction Tables, Querying Through Junction Tables); From ER Diagram to Schema, Steps 1 to 5 and Common Translation Mistakes
- Introduction to JoinsAlgoMaster SQL - selected route
Join Types Overview; Choosing the Right Join Type; Joining More Than Two Tables
- `LEFT JOIN`AlgoMaster SQL - selected route
Finding Unmatched Rows;
ONvsWHERE: The Critical Difference; Common Patterns: Counting with Zeros and Default Values withCOALESCE - `GROUP BY` and `HAVING`AlgoMaster SQL - selected route
The
SELECTRule with MySQL's RelaxedGROUP BY;HAVING: Filtering Groups;WHEREvsHAVING; SQL Execution Order withGROUP BYandHAVING;GROUP BYwith Joins: What Gets Grouped - Common Table ExpressionsAlgoMaster SQL - selected route
CTE Syntax; Multiple CTEs; CTEs vs Subqueries vs Derived Tables; Materialization Behavior
- Introduction to Window FunctionsAlgoMaster SQL - selected route
Window Functions vs
GROUP BY;PARTITION BY; CombiningPARTITION BYandORDER BY; Default Frame Behavior - `ROW_NUMBER`AlgoMaster SQL - selected route
Deduplication with
ROW_NUMBER; Top-\(N\) Per Group;ROW_NUMBERvsRANKvsDENSE_RANK
Focus on
NULLnever equals anything, not evenNULL. UseIS NULLandIS NOT NULL;COUNT(column)skips nulls whileCOUNT(*)does not.ONfilters before the outer join adds its nulls,WHEREfilters after. AWHEREcondition on the right table silently convertsLEFT JOINtoINNER JOIN.GROUP BYgrain decides counts: aggregate at the wrong grain and rows duplicate or vanish.HAVINGfilters groups after aggregation,WHEREfilters rows before.- Window functions keep row identity while
GROUP BYcollapses rows.ROW_NUMBERwithPARTITION BYgives top-\(N\) per group without losing the row.
Reproduce closed-book
HAVING, and the top track per artist with ROW_NUMBER. Predict each row count before running.Traps
- Selecting non-grouped columns under
GROUP BY. PostgreSQL rejects them (SQLSTATE 42803) unless the table's primary key is grouped, while SQLite and relaxed-mode MySQL return a value from some row of the group; list exactly the grain. - A surrogate primary key preventing logical duplicates. It does not; only a
UNIQUEconstraint on the natural key does. COUNT(*)when you meanCOUNT(column). The first counts null-extended outer-join rows, the second skips the nulls.
Practice
AlgoMaster Exercise - `LEFT JOIN`
Exercise 1: every track title with stream count, zeros included, ordered by stream_count then title
Predict the zero-count rows before running.
AlgoMaster Exercise - `GROUP BY` and `HAVING`
Exercise 2: one row per album_id with track_count and total_seconds
Add a HAVING variant that keeps only albums with more than two tracks.
Self-test
-
Tracks
LEFT JOINstreams withWHEREstreams.played_at > X. Which tracks survive?Only streamed tracks matching the filter: theWHEREremoves null-extended rows and the join behaves inner. Move the filter toONto keep all tracks. -
5 artists and 12 tracks, each track with one artist. How many rows does the inner join return, and what changes with a junction table?
12 rows: each track matches exactly one artist. A many-to-many junction fans out: one track on two playlists yields two rows. Verified on PostgreSQL 18.6 with these sizes. -
When do you use
HAVINGinstead ofWHERE?To filter on aggregates after grouping, for exampleCOUNT(*) > 2.WHEREcannot see aggregates. -
How does
ROW_NUMBERtop-1 per group differ fromGROUP BYwithmax?ROW_NUMBERkeeps the whole row including title and other columns.GROUP BYwithmaxreturns only the grouped columns plus the maximum.
Completion criterion
Stop when
Your schema rejects a duplicate-email insert and a negative amount, and your join, HAVING and ROW_NUMBER queries return the predicted counts including zeros.
Leads to
- 4.5 Transactions, isolation anomalies and ORM boundaries Guard the new schema with transactions and a retry policy.
- 4.6 Indexes, selectivity and query plans Diagnose the slow joins with plans and selectivity.
Comes after
- 4.2 JSON, JSONL, CSV, columnar formats and bounded ingestionLoad the normalized records into constrained tables.
- 4.3 Schema versions, compatibility and migrationsApply constraints and normalization to the versioned schema.
Resources for this unit
- AlgoMaster SQL - selected route Study SQL Foundations, Querying Data, Joins, Subqueries and CTEs, Window Functions, DDL, DML and Views, Performance Optimization, Transactions and Concurrency, and Data Modeling. Stop when the service queries and transaction tests pass.
Also in this section
- Official docs typed-service build path Build one record service in this order: structural protocols and generics; Pydantic models, validators, strict mode, serialization and settings; FastAPI request bodies, dependencies, errors and tests; then PostgreSQL transactions and EXPLAIN. Replay valid, malformed, duplicate and previous-schema requests through CLI and HTTP.
- PostgreSQL transactions and EXPLAIN Use the transaction tutorial and EXPLAIN guide to demonstrate commit, rollback, one isolation anomaly or prevented anomaly, and a measured plan improvement on the database actually used.
- FastAPI tutorial Use Request Body, Dependencies, Handling Errors and Testing to expose the same service layer through HTTP. Keep framework models outside the domain core and preserve the same error semantics as the CLI.
- AlgoMaster Low-Level Design - one rehearsal Use one fitting design prompt to rehearse interface contracts, dependency direction and failure ownership. Treat the review as feedback, then verify behavior with your own tests.
- Stas' Python Cookbook - data and interfaces Use Serialization and Deserialization plus Environment and Arguments for focused implementation help. Compare JSON, JSONL, CSV and compressed data, and never use pickle for untrusted interchange.
- AlgoMaster Python - interfaces, generics and data files Use Interfaces and Protocols plus Generic Types for structural typing, and Working with JSON plus Working with CSV for JSONL, error handling,
newlineandDictReaderbehavior. Lesson quizzes check recall; implementation is verified with your own scripts. - SQLAlchemy 2.1 documentation - select, Session and eager loading Use the
select()tutorial for 2.x query style,Sessionbasics for the transaction boundary and identity map, and relationship loading for lazy versusselectinloadversusjoinedloadplusraiseload. Useselect()style andSession.executeorSession.scalarsonly, never the legacyQueryAPI. - Alembic tutorial - migration scripts and downgrading Use Create a Migration Script, Running our First and Second Migration, Getting Information and Downgrading to run a paired
upgradeanddowngraderound trip on a scratch database. - PyArrow Parquet read and write docs Use Reading and Writing Single Files plus
Tablefrom_pydictandfrom_pylistto convert normalized records to Parquet and test types, nulls, column projection and schema. - Stripe idempotent requests Use the
Idempotency-Keyheader contract, stored-response replay, parameter-mismatch errors andPOST-only scope to implement keyed replay for one endpoint.