VillageSQL is a drop-in replacement for MySQL with extensions.
All examples in this guide work on VillageSQL. Install Now →
GROUP BY does. They were added in MySQL 8.0 and enable analytics queries that previously required subqueries or application-layer processing.
How Window Functions Differ from GROUP BY
GROUP BY can’t do this — it removes the individual rows.
The OVER Clause
Every window function uses anOVER clause that defines the window — the set of rows to compute over.
OVER() means the window is the entire result set.
Ranking Functions
| Function | Behavior on ties |
|---|---|
ROW_NUMBER() | Unique sequential number — ties get different numbers (arbitrary order) |
RANK() | Tied rows share a rank; next rank skips (1, 1, 3) |
DENSE_RANK() | Tied rows share a rank; next rank doesn’t skip (1, 1, 2) |
PARTITION BY: Per-Group Rankings
AddPARTITION BY to rank within groups instead of across the whole result set:
LAG and LEAD: Accessing Adjacent Rows
LAG accesses a previous row; LEAD accesses a following row — without a self-join.
Running Totals and Moving Averages
UseROWS BETWEEN to define a frame — the subset of rows to include in the calculation.
| Frame | Meaning |
|---|---|
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW | From first row to current (running total) |
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW | Current row plus 6 preceding (7-row window) |
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING | Entire partition |
Frequently Asked Questions
Can I filter on a window function result in a WHERE clause?
No — window functions are evaluated afterWHERE. To filter on a window function result, wrap the query in a subquery or CTE:
What’s the difference between ROWS and RANGE in the frame clause?
ROWS counts physical rows. RANGE groups rows with the same ORDER BY value together. For most use cases (running totals, moving averages), ROWS gives the expected behavior. RANGE can produce surprising results when there are ties in the order column.
Do window functions work with indexes?
Window functions don’t use indexes for the window computation itself, but the query can still use indexes for theWHERE clause to reduce the rows being processed. Complex window queries benefit from Reading EXPLAIN in MySQL to understand the execution plan.
Troubleshooting
| Problem | Solution |
|---|---|
ERROR 1064 on window function syntax | Window functions require MySQL 8.0 — check SELECT VERSION() |
| Window function result in WHERE clause failing | Move the filter to an outer query or CTE — WHERE can’t reference window functions |
RANK() skipping numbers unexpectedly | Expected behavior — use DENSE_RANK() if you don’t want gaps |
| Slow window function query | Add an index on the PARTITION BY and ORDER BY columns to speed up the sort |

