Course 1 of 6 · f1db dataset

Master Window Functions

Write analytical SQL without collapsing rows or resorting to application-side processing.

8Modules
6Core
1Advanced
1Architect
Module 01

Foundations of Window Functions

Core · 5 topics

Window functions compute a value for each row using a set of related rows, without collapsing them. The OVER clause turns any aggregate into a window function; PARTITION BY divides the window into independent groups.

Book reference: Chapter 18 — Understanding Window Functions

01
OVER clauseWrite aggregate expressions that return a value per row instead of per group.
02
ORDER BY within a windowControl the row sequence inside a window for order-sensitive calculations.
03
PARTITION BYSplit a dataset into independent groups for per-group window calculations.
04
Difference with GROUP BYChoose the right tool when you need group context without losing individual rows.
05
Row vs window contextAccess values from neighboring rows in the same group without a self-join.

Free preview: The OVER Clause

GROUP BY vs Window Functions
Understand when to keep all rows versus collapse them into a single group result.
Module 01 · LAG for time gap
select surname, position,
       milliseconds * interval '1ms'
         as racetime,
       interval '1ms' *
       ( milliseconds
         - lag(milliseconds, 1)
           over(order by position)
       ) as diff
  from results
       join drivers using(driverid)
 where raceid = 890
 order by position limit 5;

Five F1 finishers with their gap to the car ahead, computed from lap milliseconds.

Module 02

Ranking & Row Navigation

Core · 4 topics

ROW_NUMBER, RANK, and DENSE_RANK assign positions within an ordered partition — differing only in how they handle ties. LEAD and LAG navigate to adjacent rows without a self-join.

Book reference: Chapter 18 — Understanding Window Functions

01
ROW_NUMBERAssign a unique sequential position to each row within a group.
02
RANK & DENSE_RANKHandle tied values with gapped or continuous rank sequences.
03
NTILEDivide ordered rows into equal-sized buckets for percentile-style grouping.
04
LEAD & LAGCompare each row to its predecessor or successor without a self-join.

Free preview: ROW_NUMBER, RANK, DENSE_RANK

Window partition and order structure
See how ordering and grouping together determine each row's rank position.
Module 02 · RANK vs DENSE_RANK
select surname, laps,
       rank() over(
         order by laps desc, milliseconds
       ) as rnk,
       dense_rank() over(
         order by laps desc, milliseconds
       ) as dense,
       milliseconds * interval '1ms'
         as racetime
  from results
       join drivers using(driverid)
 where raceid = 890
 order by rnk;

Race finishers ranked by both RANK and DENSE_RANK, showing how tied rows get different position numbers.

Module 03

Running & Moving Aggregates

Core · 3 topics

SUM() OVER(ORDER BY) produces a running total; add a ROWS N PRECEDING frame and it becomes a moving window. The default frame changes when ORDER BY is present.

Book reference: Chapter 18 — Understanding Window Functions

01
SUM() OVERBuild a cumulative total that grows row by row through an ordered dataset.
02
Moving averagesSmooth noisy time-series data with a sliding fixed-width aggregate.
03
Frame clauses: ROWS vs RANGEChoose the right aggregation boundary for physical positions versus value ranges.

Free preview: Running Totals & Moving Averages

Moving window on a timeline
See how a sliding window covers a fixed band of rows at each position.
Module 03 · Running total
select x,
       sum(x) over()
         as total,
       sum(x) over(order by x)
         as running_total,
       array_agg(x) over(order by x)
         as running_set
  from generate_series(1, 5) as t(x);

Running total and accumulated array over integers 1–5, with and without ORDER BY in the window.

Module 04

Frame Semantics Deep Dive

Core · 5 topics

ROWS, RANGE, and GROUPS each define which rows enter the frame relative to the current row. Subtle differences emerge with duplicate values, and EXCLUDE lets you remove specific rows from the frame.

Book reference: Chapter 18 — Understanding Window Functions

01
UNBOUNDED PRECEDING & FOLLOWINGExtend a frame to the very first or last row of a partition.
02
CURRENT ROWAnchor one frame boundary at the row currently being evaluated.
03
Frame definitionsCombine frame mode and boundaries to control exactly which rows a function sees.
04
Frame edge cases with duplicate valuesAnticipate how duplicate ordering values affect which rows enter the frame.
05
EXCLUDE clauseExclude specific rows from the frame for self-excluding aggregates.

Free preview: Frame Semantics

Window frame specification
Understand which rows fall inside a frame relative to the current position.
Module 04 · ROWS vs RANGE
select x,
       array_agg(x) over w1
         as rows_frame,
       array_agg(x) over w2
         as range_frame
  from (select x*x
          from generate_series(1,10)
               as gs(x)
       ) t(x)
window
  w1 as (order by x
         rows  between 1 preceding
                   and 2 following),
  w2 as (order by x
         range between 10 preceding
                   and 20 following);

ROWS and RANGE frames compared on squared integers, showing which rows each mode includes per position.

Module 05

Advanced Analytical Patterns

Core · 4 topics

Sessionization, gaps & islands, and cohort analysis are classic time-series patterns. Window functions solve them in pure SQL using the running-SUM trick — no procedural loops.

Book reference: Chapter 18 — Understanding Window Functions

01
Gaps & islandsDetect contiguous sequences and breaks in ordered event data.
02
Cohort analysis with FIRST_VALUE()Anchor each cohort to its first measurement for retention and lifecycle analysis.
03
SessionizationGroup consecutive events into sessions using only pure SQL aggregation.
04
Percentiles: PERCENT_RANK, CUME_DIST, NTILEExpress each row's relative standing as a percentile or bucket within its group.

Free preview: Sessionization

Session gaps on a timeline
See how consecutive events are grouped into sessions by detecting time gaps.
Module 05 · Sessionization
with lap_flags as (
  select driverid, lap, milliseconds,
         case
           when milliseconds
                > median_ms * 1.2
           then 1 else 0
         end as is_pit
    from laptimes
         join lap_stats using(driverid)
   where raceid = 890
),
stints as (
  select driverid, lap, is_pit,
         sum(is_pit) over(
           partition by driverid
           order by lap
         ) as stint
    from lap_flags
)
select surname, stint,
       min(lap) as from_lap,
       max(lap) as to_lap,
       round(avg(milliseconds))
         * interval '1ms' as avg_lap
  from stints
       join results  using(driverid)
       join drivers  using(driverid)
 where is_pit = 0
 group by driverid, surname, stint;

Per-driver pit stints for a race, with average lap time per stint, derived from outlier lap detection.

Module 06

Performance & Query Design

Core · 4 topics

Every window query sorts its data and produces a WindowAgg node in EXPLAIN. This module covers sorting cost, spec sharing across multiple windows, and anti-patterns that force unwanted re-scans.

Book reference: Chapter 18 — Understanding Window Functions

01
Execution plans and the WindowAgg nodeRead an execution plan to identify where window sorting cost is paid.
02
Sorting costs and spec sharingWrite multiple window functions that share one sort pass for free.
03
Index considerations for window ORDER BYUse index design to eliminate the sort step from window queries.
04
Anti-patterns: filtering on window output vs source columnsFilter on source columns instead of window output to enable early predicate pushdown.

Free preview: Reading EXPLAIN

EXPLAIN output anatomy
Recognize the Sort and WindowAgg nodes that every window query produces.
Module 06 · EXPLAIN window query
explain
select surname, laps,
       row_number()
         over(order by milliseconds)
         as position,
       milliseconds * interval '1ms'
         as racetime
  from results
       join drivers using(driverid)
 where raceid = 890;

EXPLAIN output for a ranked race-result query using row_number() over milliseconds.

Module 07 · Advanced

Composing Complex Queries

Advanced · 5 topics

When a window result must be filtered or joined, wrap it in a CTE or subquery — window functions cannot appear in WHERE or HAVING. Multi-pass patterns with chained CTEs and LATERAL are covered here.

Book reference: Chapter 18 — Understanding Window Functions

01
CTEs for multi-pass computationsBreak complex multi-stage queries into readable, independently testable named steps.
02
Subquery filter patternFilter on a window's computed result by wrapping it in an outer query.
03
LATERAL joins with window functionsCombine a per-row subquery with a window function for row-aware lookups.
04
Chained window passesLayer window calculations so each pass can build on the previous result.
05
Query refactoring from correlated subqueriesReplace slow correlated subqueries with a single window scan of the dataset.

Free preview: LATERAL: Top-N Per Group

CTE pipeline
See how chained named steps build analytical results without re-scanning data.
Module 07 · Multi-pass CTE standings
with season_points as (
  select r.year, res.driverid,
         sum(res.points) as points
    from f1db.results res
    join f1db.races   r using(raceid)
   group by r.year, res.driverid
),
season_ranks as (
  select year, driverid, points,
         rank() over(
           partition by year
           order by points desc
         ) as season_rank
    from season_points
)
select d.surname,
       count(*) filter(
         where season_rank = 1
       ) as titles,
       count(*) as top3_seasons
  from season_ranks
  join f1db.drivers d using(driverid)
 where season_rank <= 3
 group by d.driverid, d.surname
 order by titles desc limit 10;

Top 10 F1 drivers by championship titles, with total top-3 season count across all seasons.

Module 08 · Architect

Designing Analytical Workloads

Architect · 5 topics

Materialized views cache expensive window computations and support incremental refresh for rolling metrics. This module covers ELT patterns and trade-offs when moving work to columnar OLAP systems.

Book reference: Chapter 18 — Understanding Window Functions

01
Materialized views for pre-computed standingsStore expensive window results physically so repeated queries pay no re-sort cost.
02
Incremental computationUpdate rolling metrics incrementally without reprocessing the entire dataset.
03
ETL/ELT patterns: deduplication, surrogate keys, pre-computationDeduplicate and pre-aggregate data as part of the data loading pipeline.
04
Trade-offs with OLAP systems (Redshift, BigQuery, DuckDB)Decide when to keep analytics in PostgreSQL versus move to a columnar engine.
05
Data architecture patternsDesign a layered data architecture that separates staging from presentation.
Data architecture layers
See how raw, integrated, and presentation layers structure an analytical pipeline.
Module 08 · ELT cumulative standings
with running as (
  select r.year, r.round,
         res.driverid,
         d.surname        as driver,
         res.points       as race_points,
         sum(res.points) over(
           partition by r.year,
                        res.driverid
           order by r.round
           rows unbounded preceding
         )                as cum_points
    from f1db.results  res
    join f1db.races    r using(raceid)
    join f1db.drivers  d using(driverid)
   where r.year = 2017
)
select year, round, driver,
       race_points, cum_points,
       rank() over(
         partition by year, round
         order by cum_points desc
       ) as standing
  from running
 order by round, standing limit 15;

Per-round championship standings for the 2017 season, with cumulative points and standing after each race.

All 6 courses. One package.

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

Get All 6 Courses Browse All 6 Courses