Course 4 of 6 · f1db, chinook, tweet, geoname

Advanced JOIN Techniques

SQL as relational algebra — all join types, LATERAL, semi/anti-joins, and recursive CTEs.

8Modules
6Core
1Advanced
1Architect
Module 01

JOIN Fundamentals

Core · 4 topics

INNER, LEFT, RIGHT, FULL OUTER, CROSS, and NATURAL joins with exact output semantics. Venn diagrams describe which rows survive — CROSS JOIN is the foundation of relational algebra, not a special case.

Book reference: Chapter 19 — Understanding Relations and Joins

01
INNER JOINKeep only rows that have a matching record in both tables.
02
USING clauseWrite cleaner join syntax when both tables share a column name.
03
LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN, CROSS JOINChoose the right join type to include or exclude unmatched rows.
04
NULL behavior in joinsPredict how NULL values affect match results in JOIN conditions.

Free preview: What is a JOIN · NULL in SQL: Three-Valued Logic

JOIN types
See which rows survive each join type at a glance.
Module 01 · Three-table join
select r.year,
       r.name         as race,
       d.surname      as driver,
       c.name         as constructor,
       res.positionorder as pos,
       res.points
  from f1db.results  res
  join f1db.races    r
    on r.raceid       = res.raceid
  join f1db.drivers  d
    on d.driverid     = res.driverid
  join f1db.constructors c
    on c.constructorid = res.constructorid
 where r.year = 2017
   and res.positionorder <= 3
 order by r.date, res.positionorder
 limit 12;

Top-three finishers per race in the 2017 season with driver and constructor names.

Module 02

Multi-Table JOIN Strategies

Core · 4 topics

Self-joins traverse hierarchies and compare rows within the same table. Non-equality joins match ranges. WITH RECURSIVE extends the pattern to arbitrary-depth graphs using SEARCH and CYCLE clauses.

Book reference: Chapter 19 — Understanding Relations and Joins

01
Progressive enrichmentBuild multi-table queries incrementally without losing track of the data shape.
02
Composite FK joinsAvoid accidental row explosions when joining on composite keys.
03
Self-JOIN and the window-function alternativeCompare or link rows within the same table.
04
Recursive reach with WITH RECURSIVE, SEARCH, and CYCLE (PostgreSQL 14)Traverse hierarchies and graphs to any depth safely.

Free preview: What is a JOIN · WITH RECURSIVE

Multi-table ERD for progressive enrichment
See how a central fact table connects to its surrounding dimension tables.
Module 02 · Self-JOIN consecutive races
select r.year,
       prev.round  as prev_round,
       regexp_replace(prev.name,
         ' Grand Prix$', '') as prev_gp,
       r.round     as curr_round,
       regexp_replace(r.name,
         ' Grand Prix$', '') as curr_gp,
       r.date - prev.date   as days_gap
  from f1db.races r
  join f1db.races prev
    on prev.year  = r.year
   and prev.round = r.round - 1
 where r.year = 2017
 order by r.round;

Days between consecutive race weekends in the 2017 Formula 1 calendar.

Module 03

JOIN Pitfalls

Core · 3 topics

Fan-out amplification happens silently when a join key is not unique on both sides. EXISTS and NOT EXISTS avoid double-counting; the NOT IN null trap is one of SQL's most common bugs.

Book reference: Chapter 19 — Understanding Relations and Joins

01
Non-equality joinsJoin tables on ranges, inequalities, or any condition — not just equality.
02
JOIN amplificationSpot when a join silently inflates your aggregate results.
03
The COUNT(DISTINCT) fix and the aggregate-first fixFix inflated aggregates caused by one-to-many join fan-out.

Free preview: JOIN Amplification & Cardinality

JOIN amplification
See how a non-unique join key silently multiplies rows.
Module 03 · JOIN amplification
-- Amplified (wrong): counts result rows,
-- not distinct constructors, because the
-- 1:N join multiplies each constructor
-- by all its result rows.
select c.name          as constructor,
       count(*)        as result_rows
  from f1db.constructors c
  join f1db.results res
    on res.constructorid = c.constructorid
 group by c.constructorid, c.name
 order by result_rows desc
 limit 5;

Race result rows per constructor ranked by total entries across all seasons.

Module 04

LATERAL JOINs

Core · 7 topics

LATERAL allows the right-hand subquery to reference columns from the left row. This enables top-N per group, kNN geographic search, and row-by-row correlated computation that a plain JOIN cannot express.

Book reference: Chapter 19 — Understanding Relations and Joins

01
LATERAL as a manual loopWrite per-row subqueries that reference the current row's values.
02
Top-N per group with LATERALRetrieve the top N related rows for every row in the outer query.
03
Reading a LATERAL execution planRead execution plans to understand how LATERAL queries are executed.
04
LATERAL over geographic data: top-N cities per countryRetrieve top-N results per group from geographic data efficiently.
05
LATERAL with distance operator: kNN geographic search (GiST <->)Find the nearest geographic points to each location in a dataset.
06
LATERAL for social graphEfficiently find the top post per user without scanning all rows.
07
Hashtag co-occurrence via array unnesting and self-joinDiscover which topics appear together most often across posts.

Free preview: LATERAL: Top-N Per Group

LATERAL join flow
Understand how LATERAL feeds each outer row into the inner subquery.
Module 04 · LATERAL Top-N per driver
select d.surname       as driver,
       top3.race,
       top3.pos,
       top3.points
  from f1db.drivers d
  join lateral (
    select r.name  as race,
           res.positionorder as pos,
           res.points
      from f1db.results res
      join f1db.races   r
        on r.raceid = res.raceid
     where res.driverid = d.driverid
       and r.year       = 2017
       and res.points   > 0
     order by res.points desc
     limit 3
  ) top3 on true
 where d.surname in (
   'Hamilton','Vettel','Bottas'
 )
 order by d.surname, top3.points desc;

Top three highest-scoring race results in 2017 for Hamilton, Vettel, and Bottas.

Module 05

Aggregation and Window Functions with JOINs

Core · 3 topics

Combining GROUP BY and window functions with JOINs requires careful ordering. FILTER and CASE inside aggregates often eliminate the need for a second join pass.

Book reference: Chapter 19 — Understanding Relations and Joins

01
GROUP BY after JOINKnow when it is safe to aggregate after joining, and when it is not.
02
Filtered aggregates with FILTER clauseCompute multiple conditional totals in a single pass over the data.
03
Window functions over joined dataApply running totals and rankings over joined, multi-table result sets.

Free preview: JOIN Amplification & Cardinality

Join amplification before aggregation
See why aggregation order relative to joins affects your totals.
Module 05 · Cumulative championship points
select d.surname              as driver,
       r.round,
       left(r.name, 14)       as race,
       res.points,
       sum(res.points) over (
         partition by d.driverid
         order by r.date
         rows between unbounded preceding
                  and current row
       )                      as cum_pts
  from f1db.results res
  join f1db.drivers d
    on d.driverid = res.driverid
  join f1db.races   r
    on r.raceid   = res.raceid
 where r.year = 2017
   and d.surname in ('Hamilton', 'Vettel')
 order by d.surname, r.date;

Cumulative championship points per race for Hamilton and Vettel across the 2017 season.

Module 06

Complex Query Composition

Core · 7 topics

WITH (CTEs), nested subqueries, and LATERAL chain together into readable multi-step computations. The MATERIALIZED option controls whether PostgreSQL treats a CTE as a fence or inlines it as a subquery.

Book reference: Chapter 19 — Understanding Relations and Joins

01
CTEs for readability and multi-step logicBreak a complex query into named, readable steps.
02
Subqueries in join positionPre-aggregate data before joining to prevent row count inflation.
03
Semi-JOIN: EXISTSFilter rows based on whether a related record exists anywhere.
04
Anti-JOIN: NOT EXISTSFind rows that have no matching record in another table.
05
The NOT IN null trapAvoid a silent bug that can wipe out all results in a filter.
06
Set operators: UNION, INTERSECT, EXCEPTCombine, overlap, or subtract result sets from multiple queries.
07
DISTINCT ONPick exactly one row per group based on a defined sort order.

Free preview: What is a JOIN · NULL in SQL: Three-Valued Logic

CTE pipeline for query composition
See how CTEs chain query steps and how EXISTS avoids the NOT IN trap.
Module 06 · Anti-JOIN NOT EXISTS
select forename, surname,
       constructors.name    as constructor,
       count(*)             as races,
       count(distinct status) as reasons
  from f1db.drivers
       join f1db.results      using(driverid)
       join f1db.races        using(raceid)
       join f1db.status       using(statusid)
       join f1db.constructors using(constructorid)
 where date >= date '1978-01-01'
   and date <  date '1978-01-01'
                + interval '1 year'
   and not exists (
     select 1
       from f1db.results r
      where position is not null
        and r.driverid = drivers.driverid
   )
 group by constructors.name, driverid
 order by count(*) desc limit 10;

Drivers who entered at least one 1978 race without ever finishing.

Module 07 · Advanced

JOIN Performance

Advanced · 5 topics

Hash Join, Nested Loop, and Merge Join — how the planner chooses and what to do when the estimate is wrong. Index-nested-loop for selective predicates, parallel hash for large equi-joins.

Book reference: Chapter 19 — Understanding Relations and Joins

01
Three join algorithms: Nested Loop, Hash Join, Merge JoinRecognize which join algorithm PostgreSQL will choose for a given query.
02
Reading execution plans with EXPLAIN (ANALYZE, BUFFERS)Read query execution plans to diagnose join performance issues.
03
Hash Join: Batches > 1 means spill to diskIdentify when a Hash Join is spilling to disk and how to fix it.
04
Index impact on join performanceUnderstand how indexes change which join algorithm PostgreSQL picks.
05
Join order and planner limits (join_collapse_limit, geqo_threshold)Control how PostgreSQL explores join orderings in complex queries.

Free preview: JOIN Amplification & Cardinality

Join operator internals
Compare the cost shape of each join algorithm at a glance.
Module 07 · EXPLAIN Hash Join
explain (analyze, buffers)
select d.nationality,
       count(distinct d.driverid)  as drivers,
       sum(res.points)             as total_points
  from f1db.results res
  join f1db.drivers d
    on d.driverid = res.driverid
 group by d.nationality
 order by total_points desc;

Execution plan for total points and driver count aggregated by nationality.

Module 08 · Architect

Architecture: Designing Join-Heavy Systems

Architect · 5 topics

Normalization vs pre-joined denormalized tables for read-heavy workloads. Foreign data wrappers for cross-schema JOINs, join planning for partitioned tables, and when to move joins out of the database.

Book reference: Chapter 19 — Understanding Relations and Joins

01
Materialized viewsCache expensive join results to serve repeated reads without recomputation.
02
Confident queries: chinook (enforced FKs) vs f1db (no FKs)Understand how foreign key declarations affect query planning.
03
Denormalization strategyEliminate a join by embedding its data directly in the querying table.
04
Access patterns drive schema designLet your most critical queries guide schema and indexing decisions.
05
Course synthesis: CTEs + aggregation-before-join + window functionsCombine CTEs, aggregation, and window functions into a complete solution.
Chinook ERD: fully-declared foreign keys
See how declared foreign keys open up planner optimizations.
Module 08 · Normalized vs pre-aggregated
-- Normalized: re-computed on every request
select c.name                           as constructor,
       count(distinct res.driverid)     as drivers,
       count(distinct res.raceid)       as races,
       count(*) filter(
         where res.positionorder = 1)   as wins,
       sum(res.points)                  as total_points
  from f1db.constructors c
  join f1db.results res
    on res.constructorid = c.constructorid
 group by c.constructorid, c.name
 order by total_points desc
 limit 10;

Constructor career totals: distinct drivers, races entered, wins, and cumulative points.

All 6 courses. One package.

Core, Advanced, and Architect tiers — 48 modules across 6 PostgreSQL courses.

Get All 6 Courses Browse All 6 Courses