Level 17: Window functions: ranking
Updated on
Pro · level 17 of 20. Goal: Number and rank rows without grouping them, overall or within each group.
Topics: OVER ROW_NUMBER RANK DENSE_RANK PARTITION BY
Lesson
GROUP BY sums up: it merges the rows of a group into one. Sometimes you want the opposite: keep all the rows and add to each one a piece of information that depends on the others: its rank, its number, its position in the group. That is the job of window functions, recognisable by the word OVER.
The “window” is the set of rows the function looks at to compute the value of the current row. OVER (ORDER BY rating DESC) means: look at all the rows, ordered by decreasing rating.
Three functions rank the rows. ROW_NUMBER() numbers 1, 2, 3… without ever repeating a number. RANK() gives the same rank to ties then skips places: 1, 2, 2, 4. DENSE_RANK() also gives the same rank to ties, but without gaps: 1, 2, 2, 3. In staff, Farid and Iris both earn 47000: RANK and DENSE_RANK put them on the same rank, ROW_NUMBER separates them.
When there is a tie, ROW_NUMBER breaks it in an arbitrary order, which may change. Add a tie-breaking column (ORDER BY salary DESC, name) to get a stable result.
PARTITION BY splits the rows into groups, and the calculation starts again in each group: RANK() OVER (PARTITION BY department ORDER BY salary DESC) ranks the employees within their department. Each department has its own number 1.
The ORDER BY written inside OVER is used for the calculation, not for the display: to sort the result, add a final ORDER BY.
Window functions are calculated after WHERE, GROUP BY and HAVING, just before the final ORDER BY. A WHERE therefore cannot filter on a rank: it does not exist yet. To keep “the first of each department”, you compute the rank in a CTE, then filter on it: this is the “top N per group” method.
Syntax
SELECT col, RANK() OVER (PARTITION BY grp ORDER BY value DESC) AS rnk
FROM my_table;Worked example
SELECT title, rating, RANK() OVER (ORDER BY rating DESC) AS rk
FROM movies;Each movie gets its rank by rating, and all 10 movies remain.
| id | title | genre | year | duration | rating | director_id |
|---|---|---|---|---|---|---|
| 1 | Night Train | Thriller | 2015 | 118 | 7.8 | 1 |
| 2 | Blue Harbor | Drama | 2018 | 102 | 7.1 | 2 |
| 3 | Paper Moon City | Comedy | 2012 | 95 | 6.4 | 5 |
| 4 | Silent Peak | Drama | 2020 | 131 | 8.2 | 3 |
| 5 | Last Signal | Sci-Fi | 2019 | 142 | 7.5 | 1 |
| 6 | Summer Keys | Comedy | 2016 | 88 | 5.9 | 4 |
| 7 | Iron Garden | Sci-Fi | 2021 | 125 | 8 | 3 |
| 8 | Dust and Gold | Western | 2014 | 110 | 6.8 | 2 |
| 9 | The Quiet Hour | Drama | 2022 | 97 | 7.4 | 5 |
| 10 | Deep Current | Thriller | 2017 | 105 | 6.9 | NULL |
Example result
| title | rating | rk |
|---|---|---|
| Silent Peak | 8.2 | 1 |
| Iron Garden | 8 | 2 |
| Night Train | 7.8 | 3 |
| Last Signal | 7.5 | 4 |
| The Quiet Hour | 7.4 | 5 |
| Blue Harbor | 7.1 | 6 |
| Deep Current | 6.9 | 7 |
| Dust and Gold | 6.8 | 8 |
| Paper Moon City | 6.4 | 9 |
| Summer Keys | 5.9 | 10 |
Key points
- OVER (…) signals a window function.
- ROW_NUMBER: unique numbers; RANK: ties then a gap; DENSE_RANK: ties without a gap.
- PARTITION BY = “within each group”.
Common pitfalls
- WHERE RANK() OVER (…) = 1 is not allowed: go through a CTE.
- The ORDER BY inside OVER orders the rows for the calculation; it doesn't sort the final result.
- ROW_NUMBER without a tie-breaker can pick a different winner from one run to the next when there's a tie.
Going further
NTILE(4) OVER (ORDER BY salary) splits the rows into 4 groups of equal size (quartiles); PERCENT_RANK gives each row's relative position, between 0 and 1.
The level’s 5 exercises
- Show the title and release date of the songs, with a column n that numbers them from oldest (1) to newest.
- Show the name and salary of the employees with their RANK and their DENSE_RANK, from highest to lowest salary.
- Show the name, department and salary of each employee with their salary rank within their department (1 = best paid in the department).
- Show the best-paid employee of each department (name, department, salary).
- For each race (race), show the two fastest runners: race, runner's name and time, sorted by race then by ranking.
In SpeedQL, every query is checked straight away: its result is compared with the expected one, then the query is run again on a hidden control database. Each exercise has written hints, to show only if you get stuck.
See also: the cheat sheet card · the SQL exercises with solutions on this topic
Do the level 17 exercises
Free, no sign-up: the lesson and the 5 exercises open right in your browser.
← Previous level: Structuring with WITH (CTE) · Next level: Window functions: running totals and averages →