ROW_NUMBER vs RANK vs DENSE_RANK: шпаргалка

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Одна секунда разницы

  • ROW_NUMBER() — каждой строке свой номер, нет повторов
  • RANK() — повторы для равных значений, пропуски следующих номеров
  • DENSE_RANK() — повторы для равных, без пропусков

Пример

Дано: зарплаты 100, 200, 200, 400.

salary ROW_NUMBER RANK DENSE_RANK
400 1 1 1
200 2 2 2
200 3 2 2
100 4 4 3

Обратите внимание:

  • ROW_NUMBER: 1, 2, 3, 4 (все разные)
  • RANK: 1, 2, 2, 4 (пропустил 3 из-за двух двоек)
  • DENSE_RANK: 1, 2, 2, 3 (без пропуска)

Синтаксис

Все три — оконные функции:

ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)
RANK()       OVER (PARTITION BY ... ORDER BY ...)
DENSE_RANK() OVER (PARTITION BY ... ORDER BY ...)

Типовые задачи

1. Топ-1 в каждой группе

Задача: последний заказ каждого пользователя.

SELECT * FROM (
    SELECT *,
        ROW_NUMBER() OVER (
            PARTITION BY user_id ORDER BY created_at DESC
        ) AS rn
    FROM orders
) t
WHERE rn = 1;

ROW_NUMBER — всегда. В группе не может быть «двух первых».

2. Топ-3, где равные = одинаковая позиция

Задача: топ-3 зарплаты, включая повторы.

SELECT * FROM (
    SELECT *,
        DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
    FROM employees
) t
WHERE rnk <= 3;

Если три человека получают 100 000 — все трое попадут в «топ-1».

3. Вторая максимальная зарплата (классика собесов)

SELECT salary FROM (
    SELECT salary,
        DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
    FROM employees
) t
WHERE rnk = 2
LIMIT 1;

Используем DENSE_RANK — иначе при дубликате максимальной зарплаты вторая не найдётся.

4. Пользователи с 3+ заказом

SELECT *
FROM (
    SELECT *,
        ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS order_num
    FROM orders
) t
WHERE order_num >= 3;

5. Dedupe: оставить 1 запись на ключ

SELECT *
FROM (
    SELECT *,
        ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at DESC) AS rn
    FROM users
) t
WHERE rn = 1;

Самая свежая запись для каждого email.

Какую функцию выбрать

Задача Функция
Номер строки / дедупликация ROW_NUMBER
Ранг с «местами» (как в спорте: 1, 2, 2, 4) RANK
Ранг без пропусков (1, 2, 2, 3) DENSE_RANK
Топ-1 ROW_NUMBER
Топ-N с учётом связок DENSE_RANK

Частые ошибки

Ошибка 1. Использовать RANK для топ-1

-- если два пользователя одновременно зашли последними,
-- RANK вернёт им обоим rn=1, и WHERE rn=1 вернёт ОБЕ строки
ROW_NUMBER() вместо RANK() при топ-1.

Ошибка 2. Неявный порядок

-- ORDER BY в окне обязателен для RANK/ROW_NUMBER
ROW_NUMBER() OVER (PARTITION BY user_id)  -- ошибка

-- правильно
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at)

Ошибка 3. Использовать WHERE вместо подзапроса

-- нельзя использовать оконную функцию в WHERE напрямую
SELECT * FROM orders
WHERE ROW_NUMBER() OVER (...) = 1;  -- ошибка

-- правильно: подзапрос или CTE
SELECT * FROM (
    SELECT *, ROW_NUMBER() OVER (...) AS rn FROM orders
) t
WHERE rn = 1;

-- или в Postgres через lateral/distinct on
SELECT DISTINCT ON (user_id) * FROM orders
ORDER BY user_id, created_at DESC;

Ошибка 4. Путаница с NULL в ORDER BY

По умолчанию NULL сортируется по-разному в разных СУБД:

  • Postgres: NULL в конце при ASC
  • MySQL: NULL в начале при ASC

Используйте NULLS FIRST / NULLS LAST:

ROW_NUMBER() OVER (ORDER BY last_login DESC NULLS LAST)

ROW_NUMBER vs DISTINCT ON (Postgres)

В Postgres есть альтернатива:

-- ROW_NUMBER подход
SELECT * FROM (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
    FROM orders
) t WHERE rn = 1;

-- DISTINCT ON (только в Postgres) — короче
SELECT DISTINCT ON (user_id) *
FROM orders
ORDER BY user_id, created_at DESC;

DISTINCT ON работает быстрее на больших данных и читается короче. Но недоступен в MySQL, ClickHouse, Snowflake — там ROW_NUMBER.

В каких СУБД работает

  • PostgreSQL, MySQL 8+, SQL Server, Oracle, BigQuery, Snowflake, ClickHouse — все три функции
  • MySQL 5.7 и ниже — нет оконных функций вообще (костыль через переменные)

Связанные темы

FAQ

Какая функция нужна для топ-N?

Обычно DENSE_RANK — корректно работает при связках. ROW_NUMBER даст неправильный ответ, если среди топа есть одинаковые значения.

Какая функция самая популярная?

ROW_NUMBER — для дедупликации и «взять первую строку группы».

В чём разница между RANK и DENSE_RANK на собесе?

RANK пропускает номера после связки: 1, 2, 2, 4. DENSE_RANK не пропускает: 1, 2, 2, 3. Часто просят написать второй по величине — там DENSE_RANK.

ROW_NUMBER быстрее RANK?

В большинстве СУБД — сопоставимо. Выбирайте по семантике, не по производительности.


Тренируйте SQL на реальных задачах — откройте тренажёр с 1500+ вопросами для собеседований аналитиков.