SQLMentor // articles

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?

MethodBest when
MAX() below MAX()You just need the value, on a database without window-function or OFFSET support
OFFSET / LIMITYou 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 subqueryYou're on an older database version without OFFSET or window functions

Frequently asked questions

What's the second-highest salary in a table where everyone earns the same amount?
With Method 1 (MAX below MAX), the query returns no rows — there is no salary less than the maximum, because everyone ties for the top. This is usually the mathematically correct answer to "second-highest distinct value", even though it can look surprising.
How do I find the Nth-highest salary, not just the second?
The OFFSET/LIMIT method generalises directly — change OFFSET 1 to OFFSET (N-1) for the Nth-highest distinct value. The DENSE_RANK method generalises too: change WHERE salary_rank = 2 to WHERE salary_rank = N.
Does this work the same way in Oracle, PostgreSQL, MySQL, and SQL Server?
The MAX-below-MAX and correlated-subquery methods are 100% portable standard SQL. DENSE_RANK works identically everywhere. OFFSET/FETCH syntax varies slightly: PostgreSQL and SQL Server use OFFSET ... FETCH NEXT, MySQL and SQLite use LIMIT ... OFFSET, and Oracle needs 12c or later for FETCH FIRST — see the pagination row in the Oracle SQL cheat sheet.
Why does my second-highest salary query return NULL instead of no rows?
If you use MIN/MAX-style aggregation and no row satisfies the WHERE condition, MAX() returns NULL (aggregates over zero rows return NULL, not an error) rather than the query returning zero rows outright. Check for this case in application code if a NULL result needs different handling than "no second value exists".