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?

Производительность примерно одинаковая. Все три требуют сортировки данных в окне. Выбирайте по смыслу, а не по скорости.