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+ вопросами для собеседований аналитиков.