Q&A library SQL

How do I rank rows within a group using a window function?

SQL Intermediate rankingwindow functions

Short answer

Use RANK, DENSE_RANK or ROW_NUMBER with PARTITION BY. The three differ only in how they treat ties, and choosing the wrong one silently changes your answer.

The short version

SELECT
    state,
    school,
    enrolment,
    ROW_NUMBER() OVER (PARTITION BY state ORDER BY enrolment DESC) AS position
FROM schools;

PARTITION BY restarts the numbering for each state. ORDER BY decides what "first" means. No GROUP BY is involved, and the detail rows survive.

The three functions, and why the choice matters

Given enrolments of 900, 850, 850, 700 within one state:

enrolment ROW_NUMBER RANK DENSE_RANK
900 1 1 1
850 2 2 2
850 3 2 2
700 4 4 3

ROW_NUMBER never ties. It will pick one of the two 850s arbitrarily, and which one it picks can change between runs unless you add a tiebreaker to the ORDER BY.

RANK ties, then skips. Two second places, no third.

DENSE_RANK ties, then continues. Two second places, then third.

If you are reporting "the top three schools" and two are tied for third, these give you different answers. Decide which is correct for the question, and say so in the report.

Getting the top n per group

WHERE cannot see a window function, because it runs before the window is computed. Wrap it.

WITH ranked AS (
    SELECT
        state,
        school,
        enrolment,
        DENSE_RANK() OVER (PARTITION BY state ORDER BY enrolment DESC) AS position
    FROM schools
)
SELECT state, school, enrolment
FROM ranked
WHERE position <= 3
ORDER BY state, position;

Make ties deterministic

If two rows tie on the ordering column, add a second column that cannot tie. Otherwise the same query returns different rows on different days, and somebody will eventually notice.

ROW_NUMBER() OVER (PARTITION BY state ORDER BY enrolment DESC, school ASC)

The mistake to avoid

SELECT state, MAX(enrolment) FROM schools GROUP BY state;   -- gives the number

That tells you the highest enrolment, not which school had it. Adding school to the select list either errors or, in MySQL's looser modes, silently returns an arbitrary school. Window functions exist because GROUP BY cannot answer "which row".

Need this solved on your own data?

The library is free and always will be. When the problem is bigger than an answer, we take on the work.

Start a project