Window functions
OVER, PARTITION BY, ROW_NUMBER, RANK, running totals, LAG/LEAD and frame clauses.
Window functions (MySQL 8.0+) perform calculations across a set of rows that are related to the current row, called its window, without collapsing rows the way GROUP BY does. They make rankings, running totals, moving averages, "top N per group" and comparisons with the previous row easy. Before MySQL 8, these needed ugly self joins or session-variable hacks.
Sample data#
The OVER clause#
Any aggregate becomes a window function when you add OVER (...):
OVER ()means the window is all rows.PARTITION BY regionsplits rows into groups, likeGROUP BY, but each row keeps its identity and sees its own group's total.
Ranking: ROW_NUMBER, RANK, DENSE_RANK#
ROW_NUMBER breaks ties arbitrarily. Add a tie-breaker (ORDER BY amount DESC, id) when you need repeatable results. Other ranking helpers include NTILE(n) (split into n buckets), PERCENT_RANK() and CUME_DIST().
Top N per group#
"The biggest sale in each region" is a classic. Window functions are computed after WHERE, so compute the rank in a CTE and filter outside:
Change rn = 1 to rn <= 3 for the top three per group. Use RANK() if ties should all be included.
Running totals and moving averages#
With ORDER BY inside OVER, an aggregate becomes cumulative:
Frames
The frame is the subset of the partition that the function looks at:
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW: the current row and the two before it (a 3-row moving window).ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW: from the start up to now (a running total).ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING: from now to the end.RANGE BETWEEN INTERVAL 7 DAY PRECEDING AND CURRENT ROW: by value (dates within the last 7 days), not by row count.
When you give ORDER BY but no frame, the default is RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. With RANGE, rows that tie on the sort key are added together. If your running total must grow row by row, use ROWS and a unique ordering.
Running total per rep:
Looking at other rows: LAG, LEAD, FIRST_VALUE#
LAG(col, n, default)reads n rows back (default 1).LEADreads forward. At the edges you getNULL, or the default you supply.FIRST_VALUE,LAST_VALUEandNTH_VALUEread a position in the frame.LAST_VALUEwith the default frame returns the current row, so give itROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.- The
WINDOW w AS (...)clause names a window definition so you don't repeat it.
Typical uses: month-over-month growth, days between a customer's orders, detecting gaps in sequences, and session analysis.
Window functions vs GROUP BY#
Use GROUP BY when you want a summary. Use window functions when you want detail rows plus context.
Where they're allowed#
Window functions are evaluated after FROM, WHERE, GROUP BY and HAVING, so they can appear only in the SELECT list and ORDER BY. You can apply them to grouped results, for example to rank regions by their total:
Common mistakes#
- Filtering on a window function in
WHERE. Wrap the query in a CTE first. - Forgetting
PARTITION BY, so ranks run across the whole table instead of per group. - Getting surprising running totals with ties because of the default
RANGEframe. UseROWS. LAST_VALUEreturning the current row, again because of the default frame.
What's next#
You've now got serious querying power. Next you'll save queries as reusable views.
Check your understanding
Quick quiz
1.How do window functions differ from GROUP BY aggregates?
2.Three people tie for first place. What does RANK() give the next person, and what does DENSE_RANK() give?
3.Why can't you write
WHERE ROW_NUMBER() OVER (...) = 1directly?
Finished reading?
Mark this lesson complete to track your progress.