В чём разница между RANK() и DENSE_RANK() в SQL?
Использую оконные функции для ранжирования и наткнулся на две похожие: RANK() и DENSE_RANK(). На первый взгляд делают одно и то же. В чём между ними разница, особенно когда есть одинаковые значения (ничьи)? И чем они отличаются от ROW_NUMBER()? Пример на SQL приветствуется.
2 ответа
Все три функции — ROW_NUMBER(), RANK(), DENSE_RANK() — ранжируют строки внутри окна, но по-разному ведут себя при ничьих (одинаковых значениях в ORDER BY).
ROW_NUMBER()— всегда уникальные номера1, 2, 3, ..., даже при равных значениях. Ничьи разрываются произвольно.RANK()— при ничьей даёт одинаковый ранг, но затем пропускает номера (есть «дырки»).DENSE_RANK()— при ничьей даёт одинаковый ранг и не пропускает следующие (без «дырок»).
Лучше всего видно на примере. Пусть есть очки игроков:
SELECT
name,
score,
ROW_NUMBER() OVER (ORDER BY score DESC) AS row_num,
RANK() OVER (ORDER BY score DESC) AS rnk,
DENSE_RANK() OVER (ORDER BY score DESC) AS dense_rnk
FROM players;
Результат для очков 100, 100, 90, 80:
name score row_num rnk dense_rnk
Anna 100 1 1 1
Boris 100 2 1 1
Clara 90 3 3 2
Dmitry 80 4 4 3
Обратите внимание на строку Clara (90 очков):
ROW_NUMBER= 3 — просто следующий по счёту.RANK= 3 — потому что два игрока заняли первое место, третий получает ранг 3 (ранг 2 пропущен).DENSE_RANK= 2 — «плотный» ранг, дырок нет, идёт сразу после первого места.
Мнемоника: RANK считает, сколько строк строго выше тебя, плюс один (поэтому есть пропуски). DENSE_RANK считает сколько различных значений выше, плюс один (поэтому пропусков нет).
Когда что брать:
- нужна спортивная таблица в стиле «1, 1, 3 место» →
RANK(); - нужны уровни/категории без пропусков (например, «топ-3 разных зарплат») →
DENSE_RANK(); - нужна строгая уникальная нумерация (дедупликация, пагинация) →
ROW_NUMBER().
Короткая шпаргалка, которую я держу в голове на собеседованиях:
Значения: 100 100 90 80
ROW_NUMBER: 1 2 3 4 (всегда подряд, уникально)
RANK: 1 1 3 4 (ничья → пропуск)
DENSE_RANK: 1 1 2 3 (ничья → без пропуска)
Хороший практический кейс для DENSE_RANK() — найти N-ю по величине различную зарплату. Например, вторая по величине зарплата:
WITH r AS (
SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS dr
FROM employees
)
SELECT DISTINCT salary
FROM r
WHERE dr = 2;
Если бы здесь стоял RANK(), то при нескольких людях с максимальной зарплатой ранг 2 мог бы вообще не существовать — и запрос вернул бы пусто. Поэтому для «N-го уникального значения» правильнее именно DENSE_RANK().