SQL Analytics — Window Functions, CTEs & Set-Based Thinking
Senior interviews fail when SQL is written as a loop over rows. Window functions, CTEs, and set-based thinking turn Top-N, running totals, and hierarchies into declarative plans. This hub maps the cluster. Indexes and EXPLAIN stay in the existing SQL Indexes pages.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
How you express Top-N, running totals, and previous-row logic
Prefer
One window over a partition
PARTITION BY, ORDER BY, and an explicit frame state the result. Tie-breaks live in ORDER BY. The optimizer gets one sort/partition pass.
- Top-N is ROW_NUMBER plus an outer filter.
- Running totals and LAG/LEAD stay at the same grain.
- Wrong frame still compiles. Name ROWS or RANGE on purpose.
Alternative
Row loops or inequality self-joins
App cursors become N+1 round-trips. A self-join on inequalities is hard to read and can explode cardinality, especially on ties.
- GROUP BY MAX cannot pick a full row when scores tie.
- CTEs name the steps for humans. Measure whether Postgres inlines them.
- Index shape is a separate lesson. Write the correct window first.
Shape first, plan second
Pick the query shape here. Read the plan in the Indexes/EXPLAIN cluster. Do not fold index design into this page.
- 1
Name the question
Top-N, running total, previous row, hierarchy, or per-row limited join. - 2
Pick the shape
Window, CTE, or LATERAL. Uniform Top-N usually wants a window. - 3
Make ties deterministic
Add a unique key to ORDER BY so retries agree. - 4
Confirm the plan
EXPLAIN (ANALYZE, BUFFERS) on the Indexes/EXPLAIN pages. This page stops at the shape.
Overview
Senior interviews and production analytics share a failure mode: treating SQL like a loop over rows. Window functions, CTEs, and set-based thinking turn Top-N, running totals, and hierarchies into declarative plans the optimizer can rewrite.
This hub maps the cluster. It does not re-teach indexes or EXPLAIN. Those live in Indexes, cardinality, and EXPLAIN and EXPLAIN and EXPLAIN ANALYZE. Use that cluster for plans. Use this cluster for query shape before you tune.
By the end you should be able to:
- Choose window vs CTE vs LATERAL from the business question.
- Say why a window beats a self-join for ranking and previous-row logic.
- Treat CTE materialization as something you measure, not something you assume.
- Point at the sibling page instead of dumping every frame rule here.
Flow
- 1
1 Name the question
- next2 Pick window, CTE, or LATERAL
- 2
2 Pick window, CTE, or LATERAL
- next3 Write the query shape
- 3
3 Write the query shape
- next4 Confirm with EXPLAIN
- 4
4 Confirm with EXPLAIN
- next5 Watch sorts and cardinality
- 5
5 Watch sorts and cardinality
Lesson map
SQL Analytics — Window Functions, CTEs & Set-Based Thinking
Senior interviews fail when SQL is written as a loop over rows. Window functions, CTEs, and set-based thinking turn Top-N, running totals, and hierarchies into declarative plans. This hub maps the cluster. Indexes and EXPLAIN stay in the existing SQL Indexes pages.
Architecture. Architecture
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB a["1 Name the question"] b["2 Pick window, CTE, or LATERAL"] c["3 Write the query shape"] d["4 Confirm with EXPLAIN"] a -->|1 Name the question| b b -->|2 Pick window, CTE, or LATERAL| c c -->|3 Write the query shape| d
Single column. The three shapes are a choice in the next section, not three swimlanes.
What this cluster covers
- Window functions —
OVER (), frames, default-frame pitfalls. - Ranking and Top-N —
ROW_NUMBER,RANK,DENSE_RANK,NTILE, PostgresDISTINCT ON. - Running totals, LAG/LEAD, and islands — cumulative sums, deltas, sessionization.
- CTEs — readability vs materialization, trees, cycle guards.
- LATERAL vs correlated subqueries — per-row Top-N, table functions, when windows win instead.
Set-based thinking vs row-by-row
- Row-by-row: app loops and cursors become N+1 round-trips. Little is shared with the optimizer.
- Self-join gymnastics: join a table to itself with inequalities. Readable poorly. Cardinality can explode.
- Set-based windows: one partition/sort pass. Clear Top-N, running totals, and lag. A wrong frame is a silent wrong answer.
- CTE staging: named steps for humans. Postgres may inline or materialize. Confirm with EXPLAIN.
Rule of thumb: prefer one sorted partition pass for ranking, running totals, and previous-row logic. Prefer CTEs for human staging, then measure materialization.
When windows beat self-joins
Classic self-join Top-1-per-group is fragile on ties. Prefer a window with a deterministic tie-break:
SELECT * FROM (
SELECT o.*,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY created_at DESC, id DESC
) AS rn
FROM orders o
) x WHERE rn = 1;Windows make the tie-break obvious and Top-k is rn <= k. They still sort and partition. Supporting keys can make that sort cheaper — that design lives in the Indexes hub. The index does not replace the right window.
When CTEs help readability vs performance
- Help: multi-step analytics, recursive org and tree walks, documenting intent.
- Watch: since Postgres 12 the planner may inline non-recursive CTEs.
MATERIALIZEDandNOT MATERIALIZEDoverride that. Confirm withEXPLAIN (ANALYZE, BUFFERS)on the EXPLAIN page.
Decision cheat sheet
| Question | Go to |
|---|---|
| Previous or next row | LAG/LEAD |
| Ranks or Top-N | Ranking |
| Hierarchy or graph walk | Recursive CTEs |
| Per outer row, limited subquery or SRF | LATERAL |
| Plan or index shape | Indexes / EXPLAIN |
Conceptual link to Indexes / EXPLAIN
Windows and LATERAL still sort, hash, and nested-loop. A Top-N window on an unindexed (customer_id, created_at) can seq-scan and sort. This cluster teaches shapes. Indexes and EXPLAIN teaches plans. Interview line: write the window for correctness, then check the plan. Do not recite access-method internals here.
Deep dive · Where the Indexes cluster fits — pointer only
Read estimates vs actuals on EXPLAIN and EXPLAIN ANALYZE. The hub for that series is Indexes, cardinality, and EXPLAIN. Stop at “the plan sorts” or “the plan nested-loops.” Column order, access methods, and statistics belong on those pages.
Sandbox: partition, then order
Same ranking idea as ROW_NUMBER() OVER (PARTITION BY cust ORDER BY ts DESC).
Press Run. Snippets must be self-contained — no network, files, or native modules.
Press Run. Snippets must be self-contained — no network, files, or native modules.
Pitfalls
For each prompt, name window, CTE, or LATERAL, then the sibling page: latest order per customer, 7-day moving average, org chart under a manager, top 3 requests for one user, consecutive active-day streaks.
Interview Q&A
What is set-based thinking in SQL?
Answer
Express the result as a relation via filters, joins, aggregates, and windows, not an imperative loop. That lets the optimizer choose strategies.
When do windows beat self-joins?
Answer
Ranking, running aggregates, LAG/LEAD, and Top-N with an explicit tie-break. The self-join version hides ties and can explode cardinality.
Do CTEs always improve performance?
How does this relate to indexes?
Answer
Windows still need supporting keys if you want a cheap sort. That is orthogonal to this cluster. Read the plan on Indexes and EXPLAIN. Do not recap access methods here.
ROW_NUMBER vs GROUP BY MAX?
Answer
MAX alone cannot pick a full row on ties. ROW_NUMBER with a deterministic ORDER BY can. Pattern: Ranking and Top-N.
When is LATERAL better than a window?
Answer
Different per-row subquery shapes or set-returning functions. For uniform Top-N across every group, windows often win. See LATERAL.
What is the default window frame pitfall?
Answer
With ORDER BY, the default is typically RANGE from unbounded preceding through the current row. Peers can merge unexpectedly. Always name the frame: Window functions.
What is the recursive CTE risk?
Answer
Cycles without a visited path or depth guard run away. Guards and UNION ALL vs UNION: Recursive CTEs.