Window Functions: ROW_NUMBER, RANK, DENSE_RANK, LEAD & LAG
Numbering and ranking rows inside partitions, top-N per group, the granularity rule, and looking at the previous or next row.
The Crux in 60 Seconds
Windows add a column, rows stay
A window function with OVER (PARTITION BY ... ORDER BY ...) works inside groups of rows without collapsing them, unlike GROUP BY.
Three numbering functions, one difference
ROW_NUMBER is always unique; RANK gives ties the same rank and skips (1, 2, 2, 4); DENSE_RANK gives ties the same rank without skipping (1, 2, 2, 3).
Filter in an outer query
The number is created in SELECT, so WHERE cannot see it. Wrap the query in a subquery or CTE and filter rn <= 2 outside.
Aggregate first, then rank
Window functions do not aggregate. For top products per category, sum sales by category and product first. LEAD and LAG fetch the next or previous row.
Foundational Concepts & Comparisons
GROUP BY
- Aggregates: one row per group, so row count drops.
- Gives max, min, average, but not the second-highest.
PARTITION BY
- Creates a window per group but keeps every row; adds a column in SELECT.
ROW_NUMBER vs RANK
- ROW_NUMBER: unique running number even for equal salaries.
- RANK: same value, same rank.
RANK vs DENSE_RANK
- RANK skips after ties: 1, 2, 2, 4.
- DENSE_RANK does not: 1, 2, 2, 3.
LEAD vs LAG
- LEAD looks at the next row in the ORDER BY sequence; LAG the previous row.
With vs without PARTITION BY
- Without it the full table is one window. With it, numbering restarts for each partition.
Raw rows vs aggregated rows
- Ranking order rows ranks orders, not products. Aggregate to product level first.
Subquery vs CTE for the filter
- Either works: generate the number in the inner part, filter in the outer query.