SQL Window Functions Cheat Sheet
Ranking, running totals and period-over-period without a self-join
Window functions compute across rows while keeping every row. They replace most self-joins and correlated subqueries, usually faster and always clearer.
Functions
| Function | Returns |
|---|---|
ROW_NUMBER()
|
A unique sequential number; ties broken arbitrarily |
RANK()
|
Ties share a rank, then the next value SKIPS (1,1,3) |
DENSE_RANK()
|
Ties share a rank, no gaps (1,1,2) |
NTILE(4)
|
Buckets the partition into quartiles |
LAG(x, 1), LEAD(x, 1)
|
The previous and next row's value |
FIRST_VALUE(x), LAST_VALUE(x)
|
Edge values — LAST_VALUE needs an explicit frame |
SUM(x) OVER (ORDER BY d)
|
Running total |
Anatomy
| Clause | Controls |
|---|---|
PARTITION BY
|
Restarts the calculation per group |
ORDER BY
|
Row order inside the partition |
ROWS BETWEEN ... AND ...
|
A frame counted in ROWS |
RANGE BETWEEN ... AND ...
|
A frame counted in VALUES of the ORDER BY column |
ROWS UNBOUNDED PRECEDING AND CURRENT ROW
|
Classic running total |
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
|
Trailing 7-row moving average |
ROWS is not RANGE
With an ORDER BY and no frame clause, the default is RANGE UNBOUNDED PRECEDING — which includes every PEER row sharing the current ORDER BY value. On a date column with several rows per day, a "running total" written that way jumps by the whole day at once. Write ROWS explicitly whenever ties are possible.
Code examples
Running total and period-over-period
One pass, no self-join. Note the explicit ROWS frame.
SELECT
order_date,
region,
revenue,
SUM(revenue) OVER (
PARTITION BY region
ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS running_total,
revenue - LAG(revenue) OVER (
PARTITION BY region ORDER BY order_date
) AS vs_previous,
DENSE_RANK() OVER (
PARTITION BY region ORDER BY revenue DESC
) AS rank_in_region
FROM orders
ORDER BY region, order_date;
Frequently asked questions
Why is my running total jumping?
You omitted the frame clause, so it defaulted to RANGE, which includes every peer row sharing the current ORDER BY value. With several rows per day the total advances a whole day at a time. Write ROWS explicitly.
RANK or DENSE_RANK?
RANK leaves gaps after a tie (1, 1, 3); DENSE_RANK does not (1, 1, 2). Use RANK for competition placing and DENSE_RANK when you need contiguous levels.
Was this cheat sheet useful?
Comments
No comments yet — be the first.
Need a different cheat sheet?
Tell us what you would like to see and we will build it — free.
Request a cheat sheet