Как найти N-ую максимальную в SQL

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

Зачем это знать

«Найди вторую по величине зарплату» — легендарный вопрос на собесах аналитика. Простой на первый взгляд, но ловушка: как обрабатывать повторы? 2-ая максимальная — это второе уникальное значение или второй ряд по убыванию?

Умение озвучить 3 способа и выбрать правильный = +1 балл за SQL-секцию.

Способ 1: DENSE_RANK (правильный)

WITH ranked AS (
    SELECT name, salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
    FROM employees
)
SELECT * FROM ranked WHERE rnk = 2;

DENSE_RANK трактует одинаковые зарплаты как одно место. «2-ая максимальная» = второе по величине уникальное значение.

Способ 2: LIMIT + OFFSET

SELECT DISTINCT salary
FROM employees
ORDER BY salary DESC
LIMIT 1 OFFSET 1;

Простой и читаемый. Работает на Postgres, MySQL, SQLite.

В MSSQL:

SELECT salary FROM (
    SELECT DISTINCT salary FROM employees
) s
ORDER BY salary DESC
OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY;

Способ 3: подзапрос

SELECT MAX(salary) FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);

Читается как «максимум того, что меньше максимума». Работает для 2-ой, но для N-ой — громоздко.

Для N-ой

С DENSE_RANK

WITH ranked AS (
    SELECT name, salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
    FROM employees
)
SELECT * FROM ranked WHERE rnk = 5;

Легко подставить любое N.

Через OFFSET

SELECT DISTINCT salary FROM employees
ORDER BY salary DESC LIMIT 1 OFFSET 4;  -- 5-ая

DENSE_RANK vs RANK vs ROW_NUMBER

Если две зарплаты одинаковые (10000, 10000, 8000):

Функция Результат
ROW_NUMBER 1, 2, 3 — уникальные номера
RANK 1, 1, 3 — ties + пропуск
DENSE_RANK 1, 1, 2 — ties без пропуска

Для «N-ая максимальная» — нужен DENSE_RANK (если ties считать одним местом).

Если ожидается несколько с одной зарплатой

Все сотрудники со 2-ой по величине зарплатой:

WITH ranked AS (
    SELECT name, salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
    FROM employees
)
SELECT name, salary FROM ranked WHERE rnk = 2;

Вернёт всех с ties.

Если нет 2-ой

Все сотрудники с одной зарплатой → 2-ой нет → результат пустой. Иногда ожидают NULL:

SELECT
    (SELECT MAX(salary) FROM employees
     WHERE salary < (SELECT MAX(salary) FROM employees))
    AS second_max;

Даже если нет — вернёт NULL, не пустой set.

На собесе

«Найди вторую максимальную зарплату».

Начните с DENSE_RANK — самое универсальное. Упомяните OFFSET как альтернативу. Уточните: «как обрабатывать ties? одинаковые зарплаты — это одно место или нет?».

Junior пишет LIMIT 2,1 без DISTINCT → не учтены ties → неправильно.

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

Расширение задачи: top-3 зарплаты в каждом department:

WITH ranked AS (
    SELECT name, department, salary,
           DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rnk
    FROM employees
)
SELECT * FROM ranked WHERE rnk <= 3;

PARTITION BY вместо общей ranking.

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

FAQ

А если нужна 1-ая?

MAX(salary). Тривиально.

Что лучше — DENSE_RANK или OFFSET?

OFFSET проще, но DENSE_RANK явно показывает ties.

Работает в MySQL 5.7?

Window functions только с 8.0. В 5.7 — OFFSET или подзапрос.


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