ROW_NUMBER vs RANK vs DENSE_RANK — в чём разница
Коротко
Все три функции нумеруют строки внутри окна. Разница — в том, как они обрабатывают одинаковые значения (tie):
- ROW_NUMBER — всегда уникальный номер, даже при одинаковых значениях
- RANK — одинаковые значения получают одинаковый ранг, следующий ранг пропускается
- DENSE_RANK — одинаковые значения получают одинаковый ранг, следующий ранг НЕ пропускается
Пример на данных
Таблица sales:
| seller | revenue |
|---|---|
| Анна | 500 |
| Борис | 500 |
| Вика | 300 |
| Глеб | 100 |
SELECT seller, revenue,
ROW_NUMBER() OVER (ORDER BY revenue DESC) AS rn,
RANK() OVER (ORDER BY revenue DESC) AS rnk,
DENSE_RANK() OVER (ORDER BY revenue DESC) AS drnk
FROM sales;Результат:
| seller | revenue | rn | rnk | drnk |
|---|---|---|---|---|
| Анна | 500 | 1 | 1 | 1 |
| Борис | 500 | 2 | 1 | 1 |
| Вика | 300 | 3 | 3 | 2 |
| Глеб | 100 | 4 | 4 | 3 |
Обратите внимание:
- ROW_NUMBER: Анна — 1, Борис — 2 (уникальные номера, порядок при tie не определён)
- RANK: Анна и Борис оба — 1, Вика — 3 (ранг 2 пропущен)
- DENSE_RANK: Анна и Борис — 1, Вика — 2 (ранги идут подряд)
Когда использовать какую функцию
ROW_NUMBER — когда нужна уникальная нумерация:
- Топ-N записей из каждой группы
- Дедупликация (оставить одну строку из дублей)
- Пагинация
-- Топ-3 товара по категориям
WITH ranked AS (
SELECT *, ROW_NUMBER() OVER (
PARTITION BY category ORDER BY sales DESC
) AS rn
FROM products
)
SELECT * FROM ranked WHERE rn <= 3;RANK — когда одинаковые значения должны иметь одинаковый ранг, а пропуски допустимы:
- Спортивные рейтинги (два первых места — следующее третье)
- Рейтинги с учётом позиции
DENSE_RANK — когда нужны ранги без пропусков:
- Группировка по рангам
- Подсчёт уникальных позиций
-- Найти 3-й по величине уникальный доход
SELECT DISTINCT revenue
FROM (
SELECT revenue, DENSE_RANK() OVER (ORDER BY revenue DESC) AS dr
FROM sales
) t
WHERE dr = 3;Ключевые отличия
| ROW_NUMBER | RANK | DENSE_RANK | |
|---|---|---|---|
| Уникальные значения | Всегда | Нет (tie = одинаковый) | Нет (tie = одинаковый) |
| Пропуски | Нет | Да | Нет |
| Максимальное значение | = кол-во строк | = кол-во строк | = кол-во уникальных значений |
| Самый частый кейс | Топ-N, дедупликация | Рейтинги | Уникальные позиции |
Ловушка с ROW_NUMBER при одинаковых значениях
Если значения совпадают, ROW_NUMBER присваивает номера произвольно. При повторном запуске порядок может измениться.
-- Опасно: при tie результат недетерминирован
ROW_NUMBER() OVER (ORDER BY revenue DESC)
-- Безопасно: добавляем tiebreaker
ROW_NUMBER() OVER (ORDER BY revenue DESC, user_id ASC)Совет для собеседования: всегда упоминайте, что ROW_NUMBER при tie недетерминирован, и предлагайте добавить дополнительную сортировку. Это показывает глубокое понимание.
Вопросы с собеседований
- «Как выбрать последний заказ каждого клиента?» — ROW_NUMBER с PARTITION BY customer_id ORDER BY created_at DESC, потом WHERE rn = 1.
- «Найдите третью по величине зарплату» — DENSE_RANK, затем WHERE dr = 3.
- «Чем ROW_NUMBER отличается от RANK при одинаковых значениях?» — ROW_NUMBER даст разные номера, RANK — одинаковые.
FAQ
Какую функцию использовать по умолчанию?
ROW_NUMBER — самая универсальная. В 80% задач на собеседованиях нужна именно она.
Можно ли использовать PARTITION BY со всеми тремя?
Да. PARTITION BY делит данные на группы, внутри каждой нумерация начинается заново.
Что быстрее — ROW_NUMBER, RANK или DENSE_RANK?
Производительность примерно одинаковая. Все три требуют сортировки данных в окне. Выбирайте по смыслу, а не по скорости.