Window Functions & OVER()
Understand what a window function is, how it differs from GROUP BY, the parts of the OVER clause, and the rules for using it.
Suppose the team analyst wants a sheet where every innings is listed and next to it the team's total, the player's rank and the running total. GROUP BY cannot do this — it squashes the rows. Window functions calculate aggregates, ranks and comparisons while keeping every row.
Definition
A window function performs a calculation across a set of rows that are related to the current row — called the window — and returns a value for every row, without collapsing the rows into groups. A function becomes a window function when it is followed by the OVER() clause.
- Related to the current row — e.g. "all innings of the same player" or "all rows up to this date".
- The window — that set of rows. It is defined inside
OVER( ... ). - For every row — 12 rows in, 12 rows out. The result is added as an extra column.
- OVER() — the keyword that turns
SUM(runs)(an aggregate) intoSUM(runs) OVER (...)(a window function).
GROUP BY vs window functions
| GROUP BY | Window function | |
|---|---|---|
| Output rows | one row per group | every input row |
| Detail rows | lost | kept |
| Mix detail + aggregate in one row | ❌ not possible | ✅ yes |
| Functions available | aggregates only | aggregates + ranking + value (LAG/LEAD…) |
| Typical use | simple summary reports | rankings, running totals, comparisons |
Same data, both ways (4 innings of MI and RCB):
sql
-- GROUP BY: 4 rows → 2 rows
SELECT team, SUM(runs) AS total
FROM innings
WHERE id IN (1, 2, 4, 5)
GROUP BY team;| team | total |
|---|---|
| MI | 121 |
| RCB | 141 |
sql
-- WINDOW: 4 rows → 4 rows, total repeated on each row
SELECT id, team, runs,
SUM(runs) OVER (PARTITION BY team) AS team_total
FROM innings
WHERE id IN (1, 2, 4, 5);| id | team | runs | team_total |
|---|---|---|---|
| 1 | RCB | 82 | 141 |
| 2 | RCB | 59 | 141 |
| 4 | MI | 45 | 121 |
| 5 | MI | 76 | 121 |
Syntax — the parts of a window function
sql
SUM(runs) OVER (
PARTITION BY player
ORDER BY match_date
ROWS UNBOUNDED PRECEDING
)| Part | Example | Job | Required? |
|---|---|---|---|
| Window function | SUM(runs) | the calculation done on the window | ✅ yes |
| OVER clause | OVER ( … ) | defines the window | ✅ yes |
| PARTITION BY | PARTITION BY player | divides rows into groups (partitions) | optional |
| ORDER BY | ORDER BY match_date | sorts rows inside each partition | optional* |
| Frame | ROWS UNBOUNDED PRECEDING | which rows of the partition to include | optional |
* ORDER BY is required for ranking and value functions (see the table below).
OVER() — empty — means the window is the ENTIRE result set. Only the function and OVER are required; everything inside the brackets is optional.
Example — OVER() on the whole result set
sql
SELECT id, player, runs,
SUM(runs) OVER () AS grand_total
FROM innings;| id | player | runs | grand_total |
|---|---|---|---|
| 1 | Virat Kohli | 82 | 645 |
| 2 | Virat Kohli | 59 | 645 |
| 3 | Virat Kohli | 73 | 645 |
| 4 | Rohit Sharma | 45 | 645 |
| 5 | Rohit Sharma | 76 | 645 |
| 6 | Rohit Sharma | 0 | 645 |
| 7 | MS Dhoni | 26 | 645 |
| 8 | MS Dhoni | NULL | 645 |
| 9 | MS Dhoni | 45 | 645 |
| 10 | Jos Buttler | 73 | 645 |
| 11 | Jos Buttler | 59 | 645 |
| 12 | Jos Buttler | 107 | 645 |
How it works: (1) empty OVER() = one big window containing all 12 rows; (2) the SUM of that window (645 — NULL ignored) is repeated on every row, and all rows are kept. Even Dhoni's NULL row gets 645.
The three families of window functions
| Family | Functions | What they answer |
|---|---|---|
| Aggregate | COUNT, SUM, AVG, MIN, MAX | totals / averages without losing rows |
| Rank | ROW_NUMBER, RANK, DENSE_RANK, NTILE, CUME_DIST, PERCENT_RANK | position of a row in an order |
| Value (analytic) | LAG, LEAD, FIRST_VALUE, LAST_VALUE | fetch a value from another row |
Which function allows which clause?
| Function | Expression | PARTITION BY | ORDER BY | Frame |
|---|---|---|---|---|
COUNT(expr) | any type | optional | optional | optional |
SUM / AVG(expr) | numeric | optional | optional | optional |
MIN / MAX(expr) | any type | optional | optional | optional |
ROW_NUMBER / RANK / DENSE_RANK() | empty | optional | required | not allowed |
CUME_DIST / PERCENT_RANK() | empty | optional | required | not allowed |
NTILE(n) | a number | optional | required | not allowed |
LEAD / LAG(expr, offset, default) | any type | optional | required | not allowed |
FIRST_VALUE / LAST_VALUE(expr) | any type | optional | required | optional |
Window function rules
- Where allowed: window functions can be used only in SELECT and ORDER BY. Not in WHERE, GROUP BY or HAVING.
- No nesting: a window function cannot be placed inside another window function —
AVG(SUM(runs) OVER ()) OVER ()is an error. - Run after WHERE: SQL filters rows with WHERE first, then calculates the window over the rows that are left.
- Filter on a window result: wrap the query in a subquery or CTE and filter in the outer query (
WHERE rn = 1).
sql
-- ❌ Error: windowed functions can only appear in the SELECT or ORDER BY clauses
SELECT id, runs
FROM innings
WHERE ROW_NUMBER() OVER (ORDER BY runs DESC) = 1;
-- ✅ Compute it in a subquery, filter outside
SELECT *
FROM (
SELECT id, runs, ROW_NUMBER() OVER (ORDER BY runs DESC) AS rn
FROM innings
) t
WHERE rn = 1;Note: an aggregate inside a window function is fine when the query uses GROUP BY:
SUM(SUM(runs)) OVER ()= "sum of the group totals". That is not nesting two window functions.
Common mistakes
- ❌ Expecting
SUM(runs) OVER ()to reduce the row count → ✅ window functions never remove rows. - ❌ Putting a window function in WHERE → ✅ use a subquery/CTE.
- ❌
RANK() OVER ()without ORDER BY → ✅ ranking needsOVER (ORDER BY …). - ❌
COUNT(DISTINCT player) OVER ()→ ✅ SQL Server does not allow DISTINCT inside a window aggregate.
Interview tip
Q: Can you use a window function in a WHERE clause? A: No. Window functions are evaluated after WHERE (and after GROUP BY/HAVING), at the SELECT step. To filter on one, calculate it in a subquery or CTE and filter in the outer query.
Key points to remember
- A window function calculates over a set of related rows (the window) and returns a value for EVERY row — rows are not collapsed.
- Any function followed by OVER() is a window function; empty OVER() = the whole result set is one window.
- Parts: function + OVER (required), PARTITION BY, ORDER BY, frame (optional).
- Three families: Aggregate (COUNT/SUM/AVG/MIN/MAX), Rank (ROW_NUMBER, RANK, DENSE_RANK, NTILE, CUME_DIST, PERCENT_RANK), Value (LAG, LEAD, FIRST_VALUE, LAST_VALUE).
- Rank and value functions REQUIRE ORDER BY inside OVER; ranking functions and LAG/LEAD do not allow a frame.
- Window functions are allowed only in SELECT and ORDER BY, cannot be nested, and run after WHERE.
- To filter on a window result, compute it in a subquery or CTE and filter outside.
Try it yourself
GROUP BY vs window — side by side
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
OVER() — grand total on every row
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Filter on a window result (subquery)
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Practice: WHERE runs before the window
Runs on a fresh IPL practice database in your browser · edit the query and press Ctrl + Enter
Revision Flashcards
Tap a card to flip it and see the answer.
