SQL Window Functions
TL;DR
Explanation
Unlike GROUP BY aggregates which reduce multiple rows to one, window functions operate over a defined frame of rows (the window) and return a value for every input row. The OVER clause defines the window via PARTITION BY (grouping), ORDER BY (row order within partition), and an optional ROWS/RANGE frame. Common functions include ROW_NUMBER (unique sequential rank), RANK/DENSE_RANK (tied ranks), LAG/LEAD (access previous/next row), and aggregate functions like SUM, AVG, MAX applied as running calculations. Window functions execute after WHERE and GROUP BY but before ORDER BY and LIMIT, making them composable with other clauses. In PHP applications they eliminate the need for multiple round trips or application-side sorting and ranking logic.
Watch Out
Common Misconception
Why It Matters
Common Mistakes
- Omitting ORDER BY inside OVER for RANK or ROW_NUMBER — the result is non-deterministic.
- Using PARTITION BY when GROUP BY is intended — window functions do not reduce rows, so counts stay per-row.
- Trying to filter on a window function alias in WHERE — use a subquery or CTE; window functions are not available in WHERE.
Avoid When
- Avoid window functions when a simple GROUP BY aggregate answers the question — window functions have higher planning cost.
- Do not use them on tables without an appropriate index on the PARTITION BY and ORDER BY columns — the sort cost dominates.
When To Use
- Use ROW_NUMBER or RANK when you need per-partition rankings without losing the underlying rows (top N per category).
- Use LAG/LEAD to compare a row with its predecessor or successor in a time series without a self-join.
- Use SUM/AVG OVER for running totals, moving averages, and cumulative distributions in reporting queries.
Code Examples
-- Application-side ranking: N+1 queries
$users = $db->query('SELECT * FROM users ORDER BY score DESC');
$rank = 1;
foreach ($users as $u) {
$u['rank'] = $rank++; // sequential but ignores ties
}
-- Single query with window function:
SELECT
id,
name,
score,
RANK() OVER (ORDER BY score DESC) AS rank,
SUM(score) OVER () AS total_score
FROM users;