← Назад к вопросам
SQL: Найти N-ую самую высокую зарплату
2.0 Middle🔥 171 комментариев
#SQL и базы данных#Аналитика и метрики
Условие
Дана таблица Employee:
CREATE TABLE Employee (
id INT PRIMARY KEY,
name VARCHAR(100),
salary DECIMAL(10,2),
department_id INT
);
Пример данных:
| id | name | salary | department_id |
|---|---|---|---|
| 1 | Alice | 100000 | 1 |
| 2 | Bob | 80000 | 1 |
| 3 | Charlie | 90000 | 2 |
| 4 | Diana | 100000 | 2 |
| 5 | Eve | 75000 | 1 |
Задание:
- Напишите запрос для получения N-ой самой высокой зарплаты (например, 2-ой)
- Обработайте случай, когда есть одинаковые зарплаты
- Что должен вернуть запрос, если N больше количества уникальных зарплат?
- Напишите решение с использованием оконных функций (DENSE_RANK)
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Задача и контекст
Нужно найти N-ую самую высокую уникальную зарплату. Это важно различать:
- N-ая самая высокая → уникальные значения (1, 2, 3, ...)
- N-ый наибольший → с учётом дубликатов
В примере уникальные зарплаты: 100000 (Alice, Diana), 90000 (Charlie), 80000 (Bob), 75000 (Eve)
- 1-ая (самая высокая): 100000
- 2-ая: 90000
- 3-ая: 80000
- 4-ая: 75000
Решение 1: С использованием LIMIT + OFFSET (простое и быстрое)
SELECT DISTINCT salary
FROM Employee
ORDER BY salary DESC
LIMIT 1 OFFSET 1; -- N-1 для получения N-ой зарплаты
Для N = 2:
SELECT DISTINCT salary
FROM Employee
ORDER BY salary DESC
LIMIT 1 OFFSET 1;
-- Результат: 90000
Объяснение:
DISTINCT— исключаем дубликаты зарплатORDER BY salary DESC— сортируем по убываниюLIMIT 1— берём одну строкуOFFSET N-1— пропускаем первые N-1 строк
Преимущества:
- Самый простой синтаксис
- Быстро выполняется
- Понимается всеми
Недостатки:
- Если N > количество уникальных зарплат → NULL (нужно обработать)
- Не очень гибкий для сложных аналитических запросов
Решение 2: С использованием DENSE_RANK (для продвинутого анализа)
SELECT salary
FROM (
SELECT
salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rank
FROM Employee
) ranked
WHERE rank = 2;
Как это работает:
employee.salary | DENSE_RANK()
100000 | 1 (Alice)
100000 | 1 (Diana) ← одинаковая, одинаковый ранг
90000 | 2 (Charlie)
80000 | 3 (Bob)
75000 | 4 (Eve)
DENSE_RANK()— в отличие отRANK(), не пропускает ранги при дубликатахOVER (ORDER BY salary DESC)— окно по всем строкам, сортировка по убываниюWHERE rank = 2— получаем строки с рангом 2
Разница между RANK и DENSE_RANK:
Rank(): DENSE_RANK():
100000 → 1 100000 → 1
100000 → 1 100000 → 1
90000 → 3 90000 → 2 ← не пропускает 2
80000 → 4 80000 → 3
75000 → 5 75000 → 4
Решение 3: Гибридный подход (параметризированный N)
DECLARE @N INT = 2;
SELECT salary
FROM (
SELECT
salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rank
FROM Employee
) ranked
WHERE rank = @N;
Для разных БД:
PostgreSQL:
PREPARE get_nth_salary (INT) AS
SELECT salary
FROM (
SELECT
salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rank
FROM Employee
) ranked
WHERE rank = $1;
EXECUTE get_nth_salary(2);
MySQL:
SET @N = 2;
SELECT salary
FROM (
SELECT
salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rank
FROM Employee
) ranked
WHERE rank = @N;
Решение 4: С использованием LEAD (для сравнения зарплат)
SELECT salary
FROM (
SELECT
salary,
LAG(salary) OVER (ORDER BY salary DESC) AS prev_salary
FROM Employee
) t
WHERE prev_salary IS NULL OR salary != prev_salary
ORDER BY salary DESC
LIMIT 1 OFFSET 1;
Как это работает:
LAG(salary) показывает предыдущую зарплату
100000 → NULL (первая)
100000 → 100000 (такая же, скипаем)
90000 → 100000 (отличается, это новый уровень)
80000 → 90000 (отличается)
75000 → 80000 (отличается)
Отфильтровываем: NULL и salary != LAG → берём только новые уровни
Решение 5: Обработка NULL при N > кол-ва уникальных зарплат
DECLARE @N INT = 5; -- Запрос на 5-ую, но уникальных только 4
SELECT
COALESCE(
(
SELECT salary
FROM (
SELECT
salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rank
FROM Employee
) ranked
WHERE rank = @N
),
NULL -- или дефолтное значение
) AS nth_highest_salary;
Результат: NULL
Альтернатива с сообщением об ошибке:
SELECT
CASE
WHEN (
SELECT COUNT(DISTINCT salary)
FROM Employee
) < @N
THEN 'N exceeds number of unique salaries'
ELSE (
SELECT salary
FROM (
SELECT
salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rank
FROM Employee
) ranked
WHERE rank = @N
)
END AS result;
Решение 6: Для получения информации о сотруднике с N-ой зарплатой
SELECT
id,
name,
salary,
department_id,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rank
FROM Employee
WHERE DENSE_RANK() OVER (ORDER BY salary DESC) = 2;
Примечание: В стандартном SQL нельзя использовать оконные функции в WHERE. Используем подзапрос:
SELECT
id,
name,
salary,
department_id,
rank
FROM (
SELECT
id,
name,
salary,
department_id,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rank
FROM Employee
) ranked
WHERE rank = 2;
Результат:
id | name | salary | department_id | rank
---|---------|--------|---------------|-----
3 | Charlie | 90000 | 2 | 2
Сравнение решений
| Метод | Читаемость | Производительность | Гибкость | Примечание |
|---|---|---|---|---|
| LIMIT/OFFSET | ✓✓✓ | ✓✓✓ | ✗ | Лучше для простых случаев |
| DENSE_RANK | ✓✓ | ✓✓ | ✓✓ | Стандартное решение |
| Параметризованный | ✓ | ✓✓ | ✓✓✓ | Для production приложений |
| LEAD/LAG | ✓ | ✓✓ | ✓ | Для анализа последовательностей |
| С проверкой NULL | ✓ | ✓ | ✓✓ | Для защиты от ошибок |
Рекомендуемое решение для production
CREATE FUNCTION getNthHighestSalary(@N INT)
RETURNS DECIMAL(10,2)
AS
BEGIN
DECLARE @result DECIMAL(10,2);
SELECT @result = salary
FROM (
SELECT
salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rank
FROM Employee
) ranked
WHERE rank = @N;
RETURN @result;
END;
-- Использование
SELECT dbo.getNthHighestSalary(2) AS second_highest;
Тест на примере
Запрос:
SELECT salary
FROM (
SELECT
salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rank
FROM Employee
) ranked
WHERE rank = 2;
Выполнение:
employee: rank calculation: filtering (rank=2):
100000 (Alice) → rank=1
100000 (Diana) → rank=1
90000 (Charlie) → rank=2 ✓ ← результат
80000 (Bob) → rank=3
75000 (Eve) → rank=4
Результат: 90000
Итоговый чеклист
- Используй DENSE_RANK() для N-ой самой высокой уникальной зарплаты
- OFFSET/LIMIT для простых случаев
- Оборачивай в подзапрос для фильтрации по окну
- Обработай случай N > кол-ва уникальных значений
- Для production используй параметризованные функции
- Помни: RANK() пропускает, DENSE_RANK() не пропускает номера
- На больших таблицах индекс на salary улучшит производительность