How to Find the Second Highest Salary in SQL (5 Ways)
On this page 11 sections ▾
- The data we are working with
- Method 1: the subquery everyone reaches for first
- Method 2: ORDER BY with LIMIT and OFFSET
- Method 3: count how many salaries are above you
- Method 4: a window function (the modern answer)
- Method 5: the Nth highest, once and for all
- The second highest salary per department
- Which answer should you give?
- Practice this topic
- Frequently asked questions
- 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_name | last_name | salary |
|---|---|---|
| Chen | Wei | 156000 |
| Ananya | Rao | 142000 |
| Tomas | Nowak | 131000 |
| Marcus | Bennett | 118000 |
| Priya | Menon | 105000 |
| Leila | Haddad | 96000 |
| Isabela | Moreira | 92000 |
| Grace | Okafor | 89500 |
| Sofia | Lindqvist | 79000 |
| Hiroshi | Tanaka | 74500 |
| Daniel | Cruz | 68000 |
| Omar | Farouk | 61000 |
● 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_name | salary |
|---|---|
| Ananya | 142000 |
● 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:
| Function | Numbers produced | Second highest gives you |
|---|---|---|
| ROW_NUMBER() | 1, 2, 3 | the second person on 100 |
| RANK() | 1, 1, 3 | nothing — rank 2 does not exist |
| DENSE_RANK() | 1, 1, 2 | the 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:
| Approach | Change this |
|---|---|
| LIMIT with OFFSET | OFFSET N - 1 |
| Correlated count | N = (SELECT COUNT(DISTINCT ...)) |
| DENSE_RANK in a CTE | WHERE salary_rank = N |
| Nested MAX | nest one more level per position (do not do this past 2) |
| Sort in the application | no 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:
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.
Related reading
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.