Second Highest Salary

An easy SQL interview practice problem on DataDriven. Write the query and run it against a real database, with instant feedback.

Domain
SQL
Difficulty
easy
Seniority
L3
Asked in
Munich Re

Problem

HR is setting the salary band for a new role and needs the second-highest unique salary on record as a benchmark. If all employees share the same salary, return NULL.

Silver medal. Almost the top, but not quite.

Tables

employees

Columns: employee_id INTEGER, emp_name TEXT, salary REAL, department TEXT, manager_id INTEGER, hire_date TEXT, user_id INTEGER

employees: 10 of 200 rows
employee_idemp_namedepartmentsalarymanager_idhire_dateuser_id
1Alice ChenEngineering55731NULL2021-02-02100
2Bob MartinezProduct61462NULL2022-03-03197
3Carol WuDesign67193NULL2023-04-04294
4David KimMarketing72924NULL2024-05-05391
5Eve JohnsonSales78655NULL2025-06-06488
6Frank PatelHR84386NULL2026-07-07585
7Grace LeeFinance55117NULL2020-08-08682
8Hank BrownOperations60848NULL2021-09-09779
9Iris DavisData66579NULL2022-10-10876
10Jake WilsonLegal72310NULL2023-11-11973

Expected output

First 1 of 1 rows
second_highest
145790

Worked solution and explanation

This is a distinct-value selection dressed up as a compensation benchmark. The real question: can you return the second-highest salary value, not the second row, and still hand back exactly one NULL row when no such value exists? Everyone reaches for ORDER BY salary DESC LIMIT 1 OFFSET 1. On this table that is wrong: every salary sits on two rows, so the top figure, 150907, also fills row two, and the benchmark HR receives is the maximum itself.

The trap

Second row
ORDER BY salary DESC LIMIT 1 OFFSET 1 counts rows. Two rows share 150907, so it returns 150907. If every salary were equal it would return that salary, and a DISTINCT variant would return zero rows. Neither is the single NULL row the spec asks for.
Second value
MAX(salary) over the rows strictly below the top keeps every copy of 150907 out at once and returns 145790. If nothing is below the top, the aggregate still emits one row, and that row is NULL.

Building it

Step 1: Find the ceiling once

(SELECT MAX(salary) FROM employees) is a scalar subquery: one number, computed once, that every outer row is compared against. No join, no ranking.

Step 2: Keep only what sits strictly below it

WHERE salary < (...) removes every row that ties the top, however many there are. The strict less-than is what collapses the ties; you never need DISTINCT.

Step 3: Take the maximum of what is left

MAX(salary) over the surviving rows is the second distinct value. With no GROUP BY, an aggregate over zero rows still returns one row holding NULL, so the all-equal case needs no extra code.

Second-highest distinct salary
SELECT MAX(salary) AS second_highest
FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees)

Common follow-up questions