Second Highest Salary in SQL: 4 Ways to Find It
This is one of the most-asked SQL interview questions, and most explanations skip the one thing that actually matters: what "second-highest" means when salaries tie. Here are four working solutions, what each one actually answers, and how to pick the right one.
The question, and the hidden ambiguity
"Find the second-highest salary" is one of the most-asked SQL interview questions — and most people jump straight to writing a query without settling the one thing that actually matters: does "second-highest" mean the second-highest distinct value, or literally the second row when sorted?
If two employees both earn the company's top salary, are they both "first", making the next distinct amount "second"? Or is the second row in sorted order just... the second person tied for first? Different interviewers (and different real-world requirements) mean different things. This article covers both, plus the four ways people commonly solve it.
Method 1: MAX() below the MAX() (second-highest distinct value)
The simplest approach — find the largest salary that is less than the overall largest salary. This naturally answers "second-highest distinct value" and handles ties correctly.
SELECT MAX(salary) AS second_highest
FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);
Why this handles ties correctly
If three employees tie for the highest salary, this query still returns the correct second-highest distinct value — because it explicitly excludes rows equal to the maximum, not just "the top row". It's also the easiest version to extend: change < to a nested subquery to get the third-highest, fourth-highest, and so on (see Method 2).
Method 2: OFFSET / LIMIT (Nth-highest, any N)
Sort descending, skip the top N-1 distinct values, take the next one. This generalises cleanly to "find the Nth-highest salary".
-- PostgreSQL / SQL Server / Oracle 12c+
SELECT DISTINCT salary
FROM employees
ORDER BY salary DESC
OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY;
-- MySQL / SQLite
SELECT DISTINCT salary
FROM employees
ORDER BY salary DESC
LIMIT 1 OFFSET 1;
Why DISTINCT matters here
Without DISTINCT, this method answers the other interpretation of the question — the second row in sorted order, which could be a duplicate of the highest salary if there's a tie. Include DISTINCT if you want distinct salary levels; drop it if "second row" (including ties) is genuinely what's being asked.
Method 3: DENSE_RANK() (best for "top N salaries, all ties included")
A window function makes the ranking explicit and is the clearest way to return every employee who shares the second-highest salary — not just the number.
SELECT first_name, last_name, salary
FROM (
SELECT first_name, last_name, salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS salary_rank
FROM employees
)
WHERE salary_rank = 2;
Why DENSE_RANK, not RANK
Use DENSE_RANK(), not RANK(), for this pattern. RANK skips numbers after ties (1, 2, 2, 4), so if two people tie for the highest salary, RANK never produces a rank of 2 at all — the query above would return zero rows. DENSE_RANK never skips (1, 2, 2, 3), so it reliably finds "the second distinct salary level" regardless of how many people tie for first. See the full ROW_NUMBER vs RANK vs DENSE_RANK comparison.
Method 4: Correlated subquery (portable to almost any SQL dialect)
Counts how many distinct salaries are strictly greater than each row's salary — the second-highest has exactly one salary above it. Uses no dialect-specific limiting clause at all, so it runs unchanged on Oracle, PostgreSQL, MySQL, and SQL Server.
SELECT DISTINCT salary
FROM employees e1
WHERE 1 = (
SELECT COUNT(DISTINCT e2.salary)
FROM employees e2
WHERE e2.salary > e1.salary
);
Which method should I use?
| Method | Best when |
|---|---|
| MAX() below MAX() | You just need the value, on a database without window-function or OFFSET support |
| OFFSET / LIMIT | You need the Nth-highest for an arbitrary N, and duplicates as "one value" is fine |
| DENSE_RANK() | You need the employees, not just the number — and want ties handled correctly |
| Correlated subquery | You're on an older database version without OFFSET or window functions |