Классика: вторая по величине зарплата

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

«Вторая по величине зарплата» — задача-проверка: важно учесть дубликаты (две одинаковые максимальные — это всё ещё одно «место») и пустой результат.

Подвох задачи

«Вторая зарплата» — это не вторая строка после сортировки, а второе уникальное значение. Если максимум получают двое, вторая по величине — это следующее меньшее значение, а не повтор максимума. Покажем разные подходы на данных, где максимум дублируется.

Способ 1: подзапрос с MAX

Идея: «максимум среди тех, кто меньше общего максимума». Коротко и без оконных функций:

CREATE TABLE emp (id INTEGER PRIMARY KEY, salary INTEGER);
INSERT INTO emp (salary) VALUES (120000), (120000), (100000), (90000);

SELECT MAX(salary) AS vtoraya
FROM emp
WHERE salary < (SELECT MAX(salary) FROM emp);

Вывод:

100000

Общий максимум 120000 (его получают двое), а максимум среди меньших — 100000. Это и есть вторая по величине. Дубликат максимума не помешал.

Способ 2: DISTINCT + ORDER BY + LIMIT/OFFSET

Берём уникальные значения, сортируем по убыванию и пропускаем первое. OFFSET 1 LIMIT 1 = «вторая строка»:

CREATE TABLE emp (id INTEGER PRIMARY KEY, salary INTEGER);
INSERT INTO emp (salary) VALUES (120000), (120000), (100000), (90000);

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

Вывод:

100000

DISTINCT убирает дубль 120000, сортировка ставит значения по убыванию, OFFSET 1 пропускает первое (максимум), LIMIT 1 берёт следующее — 100000. Способ читаемый, но LIMIT/OFFSET — это про N-ю строку, помните про DISTINCT, иначе посчитаете дубли.

Способ 3: DENSE_RANK

Самый масштабируемый, легко обобщается на N-ю зарплату. DENSE_RANK присваивает уникальным значениям места без пропусков — берём место 2:

CREATE TABLE emp (id INTEGER PRIMARY KEY, name TEXT, salary INTEGER);
INSERT INTO emp (name, salary) VALUES
    ('Аня',120000), ('Борис',120000), ('Вера',100000), ('Гена',90000);

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

Вывод:

Вера|100000

Здесь DENSE_RANK даёт обоим стотысячникам место 1, значению 100000 — место 2. Меняя mesto = 2 на любое N, получаем N-ю по величине. И заметьте: способ вернул имя сотрудника, а не только число.

Способ 4: коррелированный подзапрос

Идея на чистом SQL без оконных функций: «зарплата, выше которой ровно одно уникальное значение». Полезно знать для собеседований, где оконные функции «нельзя»:

CREATE TABLE emp (id INTEGER PRIMARY KEY, salary INTEGER);
INSERT INTO emp (salary) VALUES (120000), (120000), (100000), (90000);

SELECT DISTINCT salary AS vtoraya
FROM emp e
WHERE (SELECT COUNT(DISTINCT salary) FROM emp e2 WHERE e2.salary > e.salary) = 1;

Вывод:

100000

Для зарплаты 100000 выше неё ровно одно уникальное значение (120000) — условие выполнено. Обобщается на N-ю: условие = N-1.

Итог

  • «Вторая зарплата» — это второе уникальное значение, а не вторая строка.
  • Способы: MAX среди меньших; DISTINCT + LIMIT/OFFSET; DENSE_RANK = 2; коррелированный подзапрос со счётчиком.
  • DENSE_RANK легко обобщается на N-ю и умеет вернуть саму строку, а не только число.
Проверьте себя
1. Что значит «вторая по величине зарплата», если максимум получают двое?
AВторая строка после сортировки (повтор максимума)
BВторое уникальное значение — следующее меньшее
CСреднее двух максимумов
DЛюбая из двух максимальных
2. Почему в способе с LIMIT/OFFSET важен DISTINCT?
AДля скорости
BБез него дубликаты максимума займут места, и OFFSET укажет не туда
CDISTINCT обязателен для LIMIT
DЧтобы избежать ошибки синтаксиса
3. Чем удобен DENSE_RANK для этой задачи?
AОн быстрее всех
BЛегко обобщается на N-ю зарплату и возвращает саму строку, а не только число
CНе требует таблицы
DИгнорирует ORDER BY
Поддержать проект