Interview prep

How to Find the Second Highest Salary in SQL (5 Ways)

9 min readUpdated September 10, 2026Every example verified
On this page 11 sections ▾
  1. The data we are working with
  2. Method 1: the subquery everyone reaches for first
  3. Method 2: ORDER BY with LIMIT and OFFSET
  4. Method 3: count how many salaries are above you
  5. Method 4: a window function (the modern answer)
  6. Method 5: the Nth highest, once and for all
  7. The second highest salary per department
  8. Which answer should you give?
  9. Practice this topic
  10. Frequently asked questions
  11. Related reading

“Find the second highest salary” is probably the most asked SQL interview question there is, and it is asked for a good reason: there is no single keyword for it. You have to combine things you already know, and the way you combine them says something about how well you understand SQL.

This page shows five approaches that all work, explains what each one actually does, and then answers the two follow-up questions that separate a memorised answer from an understood one: what happens with ties, and how to generalise to the Nth highest. Every query below runs against this site’s sample Employees table — press Run on any of them and change the numbers.

The data we are working with

Twelve employees, each with a salary. Here they are sorted from highest to lowest, so you can check every answer below by eye:

SELECT first_name, last_name, salary
FROM Employees
ORDER BY salary DESC;
first_namelast_namesalary
ChenWei156000
AnanyaRao142000
TomasNowak131000
MarcusBennett118000
PriyaMenon105000
LeilaHaddad96000
IsabelaMoreira92000
GraceOkafor89500
SofiaLindqvist79000
HiroshiTanaka74500
DanielCruz68000
OmarFarouk61000

12 rows · produced by running this query on the sample database

The highest salary is the first row. The second highest is the second row. Now let us get there with SQL instead of with our eyes.

Method 1: the subquery everyone reaches for first

The idea in one sentence: find the largest salary that is smaller than the largest salary.

SELECT MAX(salary) AS second_highest
FROM Employees
WHERE salary < (SELECT MAX(salary) FROM Employees);
second_highest
142000

1 row · produced by running this query on the sample database

The inner query runs first and collapses to a single number — the top salary. The outer query then ignores everybody on that number and takes the maximum of what is left. Two passes over the table, no sorting, and it works on every database ever made.

This is the answer to give when someone says “without using LIMIT or window functions”, which is a common way to make the question harder.

Why this one handles ties well

If three people share the top salary, they are all excluded together, and you get the next distinct salary down. That is almost always what “second highest” is meant to mean. Keep this in mind — the LIMIT approach below behaves differently.

Method 2: ORDER BY with LIMIT and OFFSET

Sort descending, skip one row, take one row:

SELECT first_name, salary
FROM Employees
ORDER BY salary DESC
LIMIT 1 OFFSET 1;
first_namesalary
Ananya142000

1 row · produced by running this query on the sample database

Read LIMIT 1 OFFSET 1 as “skip 1, then give me 1”. It is the shortest answer and the easiest to adapt: OFFSET 2 gives the third highest, OFFSET 4 the fifth.

It also has the biggest trap on this page. It returns the second highest row, not the second highest salary. If two people earn the top amount, the row you get back is the second of those two — still the top salary. Add DISTINCT when you want the second distinct value:

SELECT DISTINCT salary
FROM Employees
ORDER BY salary DESC
LIMIT 1 OFFSET 1;
salary
142000

1 row · produced by running this query on the sample database

One more thing worth knowing for interviews: this syntax is not universal. SQL Server writes OFFSET 1 ROWS FETCH NEXT 1 ROWS ONLY, and Oracle before 12c used a ROWNUM wrapper. The dialect differences page lists the row-limiting syntax for each database side by side.

Method 3: count how many salaries are above you

This one looks strange the first time and then never leaves your toolbox. For each salary, count the distinct salaries greater than or equal to it. The second highest salary is the one where that count is exactly 2:

SELECT DISTINCT e.salary
FROM Employees e
WHERE 2 = (
    SELECT COUNT(DISTINCT x.salary)
    FROM Employees x
    WHERE x.salary >= e.salary
);
salary
142000

1 row · produced by running this query on the sample database

The inner query mentions e.salary from the outer query, which makes it a correlated subquery: it is evaluated once per row rather than once in total. Slow on a large table, but it generalises perfectly — change 2 to 5 and you have the fifth highest, with ties handled the way you usually want because of the COUNT(DISTINCT ...).

Method 4: a window function (the modern answer)

If the interview allows window functions, this is the answer that shows you know the language as it is today. DENSE_RANK() numbers the salaries from the top, giving equal salaries the same number and leaving no gaps:

WITH ranked AS (
  SELECT first_name, salary,
         DENSE_RANK() OVER (ORDER BY salary DESC) AS salary_rank
  FROM Employees
)
SELECT first_name, salary
FROM ranked
WHERE salary_rank = 2;

Two details that get asked about immediately. First, the ranking has to happen in a CTE or subquery, because WHERE runs before window functions are computed — you cannot write WHERE salary_rank = 2 in the same SELECT that creates it. Second, the choice of ranking function matters:

Three ranking functions on the salaries 100, 100, 90
FunctionNumbers producedSecond highest gives you
ROW_NUMBER()1, 2, 3the second person on 100
RANK()1, 1, 3nothing &mdash; rank 2 does not exist
DENSE_RANK()1, 1, 2the person on 90

That table is the real content of the question. DENSE_RANK() is the right default for “second highest salary”; RANK() silently returns zero rows when the top value is tied, which is a bug waiting to happen. The window functions guide works through all three.

This block does not run in the playground

The in-browser engine this site ships supports ROW_NUMBER() but not RANK() or DENSE_RANK(), so that example is marked needs a full database rather than given a Run button that would only produce an error. It is correct SQL on PostgreSQL, MySQL 8+, SQL Server, Oracle and SQLite 3.25+.

Method 5: the Nth highest, once and for all

Every method above is a special case of the same question. Here is each one written so that changing a single number gives you any position you like:

Nth highest salary, five ways
ApproachChange this
LIMIT with OFFSETOFFSET N - 1
Correlated countN = (SELECT COUNT(DISTINCT ...))
DENSE_RANK in a CTEWHERE salary_rank = N
Nested MAXnest one more level per position (do not do this past 2)
Sort in the applicationno SQL change - but you moved the work to the wrong place

The third highest, using the version that runs here:

SELECT DISTINCT salary
FROM Employees
ORDER BY salary DESC
LIMIT 1 OFFSET 2;
salary
131000

1 row · produced by running this query on the sample database

And the version that survives a follow-up question about ties, with the count changed to 3:

SELECT DISTINCT e.salary
FROM Employees e
WHERE 3 = (
    SELECT COUNT(DISTINCT x.salary)
    FROM Employees x
    WHERE x.salary >= e.salary
);
salary
131000

1 row · produced by running this query on the sample database

The second highest salary per department

This is the follow-up you should expect, and it is where window functions stop being optional. Partition the ranking by department and the same query answers it for every department at once:

WITH ranked AS (
  SELECT d.dept_name, e.first_name, e.salary,
         DENSE_RANK() OVER (PARTITION BY e.dept_id ORDER BY e.salary DESC) AS salary_rank
  FROM Employees e
  JOIN Departments d ON d.dept_id = e.dept_id
)
SELECT dept_name, first_name, salary
FROM ranked
WHERE salary_rank = 2
ORDER BY dept_name;

PARTITION BY restarts the numbering for each department, so rank 2 means “second highest within this department”. Without window functions you would need a correlated subquery per department, which is the honest answer to give if the interviewer says the database is old.

Which answer should you give?

  • If they say “any way you like”: the LIMIT/OFFSET version with DISTINCT. It is short and obviously correct, and you can explain it in one sentence.
  • If they say “no LIMIT”: the nested MAX. Two lines, works everywhere, handles ties.
  • If they say “now the Nth” or “per department”: DENSE_RANK in a CTE. Say out loud why the rank has to be computed in a subquery.
  • If they say “what about ties?”: this is the real question. Explain that “second highest salary” and “the person with the second highest salary” are different questions, and that DISTINCT or DENSE_RANK answers the first while plain OFFSET answers the second.

The best answer in an interview is usually two methods and the trade-off between them, not one method delivered fast.

Practice this topic

Reading explains the idea; producing it yourself is what makes it stick. These exercises use exactly what this page covers, and your answer is checked by running it against the same sample database:

See all 42 exercises →

Frequently asked questions

How do you find the second highest salary in SQL?

The shortest way is SELECT DISTINCT salary FROM Employees ORDER BY salary DESC LIMIT 1 OFFSET 1, which sorts the distinct salaries from highest to lowest, skips the top one and returns the next. Without LIMIT, use SELECT MAX(salary) FROM Employees WHERE salary < (SELECT MAX(salary) FROM Employees), which takes the largest salary below the largest salary.

How do you find the Nth highest salary in SQL?

Use ORDER BY salary DESC LIMIT 1 OFFSET N-1, or rank the rows with DENSE_RANK() OVER (ORDER BY salary DESC) inside a CTE and filter WHERE salary_rank = N. Both let you change a single number to get any position, which nested MAX subqueries do not.

What happens if two employees have the same highest salary?

It depends on the method. LIMIT 1 OFFSET 1 returns the second row, which is still the top salary, so it answers "the second highest paid person". Adding DISTINCT, or using DENSE_RANK, returns the next distinct amount instead, which answers "the second highest salary". Decide which question is being asked before choosing.

Should I use RANK or DENSE_RANK for the second highest salary?

DENSE_RANK. RANK leaves gaps after a tie, so if two people share the top salary the ranks are 1, 1, 3 and filtering on rank = 2 returns no rows at all. DENSE_RANK produces 1, 1, 2 and gives the answer you expect.

Can I find the second highest salary without a subquery?

Yes, with ORDER BY salary DESC LIMIT 1 OFFSET 1, which needs no subquery at all. Every other approach needs either a subquery or a window function, because SQL has no single operator for "second largest".

Why does WHERE salary_rank = 2 not work in the same query?

Because window functions are computed after WHERE has already run, so the rank does not exist yet when WHERE is evaluated. Put the window function in a CTE or subquery and filter it in the outer query.

About the author

SQL Practice

SQL Practice publishes free SQL tutorials, an in-browser playground and 42 practice exercises, built so you can run what you read. Every runnable example in our tutorials is executed against the site’s sample database before it is published. Read how we keep tutorials accurate, or report a mistake.