Course 4 of 6 · f1db, chinook, tweet, geoname
SQL as relational algebra — all join types, LATERAL, semi/anti-joins, and recursive CTEs.
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
Free preview: What is a JOIN · NULL in SQL: Three-Valued Logic
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.
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
Free preview: What is a JOIN · WITH RECURSIVE
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.
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
Free preview: JOIN Amplification & Cardinality
-- 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.
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
Free preview: LATERAL: Top-N Per Group
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.
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
Free preview: JOIN Amplification & Cardinality
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.
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
Free preview: What is a JOIN · NULL in SQL: Three-Valued Logic
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.
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
Free preview: JOIN Amplification & Cardinality
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.
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
-- 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.
Core, Advanced, and Architect tiers — 48 modules across 6 PostgreSQL courses.