Step 1 Solve it yourself first
Tables you will use
What it really tests
Whether you think about ties and about "no answer". 1 OFFSET 1 is wrong when two people share the top salary, and many answers return no at all instead of .
The pattern: Nth highest value
The words "second highest", "Nth largest", "third most recent". The answer is a value, not a person, so ties must collapse: think or .
Picture it
flowchart LR S["salaries high to low<br/>95000 · 80000 · 70000 · 70000 · 60000 …"] --> R["DENSE_RANK<br/>1 · 2 · 3 · 3 · 4 …"] R --> K["keep rank 2<br/>80000"]
The approach, step by step
- Ask: if two people tie for the highest salary, is the "second highest" the next different amount? (Usually yes.)
- Rank the distinct salaries from highest down, so ties share a rank and no number is skipped.
- Keep the salary ranked 2.
- Wrap it so that "no such salary" gives one row containing NULL, not an empty result — an like does this.
Your turn: write it before you look
Your queries in this box
Show the solution
Stuck for more than ten minutes? Open it, read it, close it, and write it again from memory.
Solution: DENSE_RANK, wrapped in MAX
WITH ranked AS (
SELECT salary,
DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk
FROM employees
)
SELECT MAX(salary) AS second_highest
FROM ranked
WHERE rnk = 2;1 row
| second_highest |
|---|
| 80000 |
MAX over no rows returns NULL, so if there were no second salary you would still get one row with NULL. Change 2 to N for the Nth highest.
Another way: Without : the highest below the highest
SELECT MAX(salary) AS second_highest
FROM employees
WHERE salary < (SELECT MAX(salary) FROM employees);1 row
| second_highest |
|---|
| 80000 |
Short and clear for "second", but it doesn't generalise neatly to the Nth.
Another way: DISTINCT with LIMIT and OFFSET, inside a
SELECT (
SELECT DISTINCT salary
FROM employees
ORDER BY salary DESC
LIMIT 1 OFFSET 1
) AS second_highest;1 row
| second_highest |
|---|
| 80000 |
DISTINCT makes ties collapse; the outer turns "no row" into NULL.
Edge cases to say out loud
- Ties for first: two people on 95000 — the answer is still the next different salary, 80000.
- Fewer than two different salaries: return NULL, not an empty result.
- NULL salaries: ignored by ranking and MAX, which is usually what you want — say so.
Common mistakes
ORDER BY salary DESC LIMIT 1 OFFSET 1without DISTINCT: returns the top salary again when two people tie for first.- Using : after a tie for first, there is no rank 2.
- Returning an empty result when the question asked for NULL.
Step 2 Go further (free, optional)
Optional. The notes above are written to be enough on their own. These free resources give a second explanation and more practice.
-
Practice
LeetCode — Top SQL 50 study plan
free account
-
Practice
DataLemur — second highest salary
real interview questions, many free
Step 3 Practise
Try each one yourself first: out loud, or written and run in the practice box below, which saves every query you run. Then open the answer and compare.
-
ExplainSay what it does and when to use it, in plain words, with no notes.
Explain why LIMIT 1 OFFSET 1 can give the wrong answer here, using a tie example.
Show answer
ORDER BY salary DESC LIMIT 1 OFFSET 1takes the second row, not the second salary. With a tie at the top, the second row has the same salary as the first.If Hana's salary were 95,000 too, the sorted salaries would be 95,000, 95,000, 70,000… The second row is 95,000: the highest again, not the second highest (which is 70,000). The below makes that change and shows the wrong answer.
It has a second problem: when there is no second row, it returns no rows at all, while the question asks for NULL.
SELECT DISTINCTinside a scalar subquery fixes both, and DENSE_RANK = 2 is clearer still.sqlUPDATE employees SET salary = 95000 WHERE name = 'Hana'; SELECT salary AS not_second_highest FROM employees ORDER BY salary DESC LIMIT 1 OFFSET 1;1 row
not_second_highest 95000 -
Writefrom a blank page, then run it
Return the third highest salary, and then the second highest salary within each department.
Show answer
The third highest is the same query with
WHERE rnk = 3: 70,000 (after 95,000 and 80,000; Ben and Chen share it, and DENSE_RANK counts it once).Per department, add dept_id so each department ranks its own salaries. Starting from departments with a , and putting
rnk = 2in the , keeps departments with no second salary: Marketing (only Fatima) and HR (nobody) show NULL, as the original question asks.sqlWITH ranked AS ( SELECT dept_id, salary, DENSE_RANK() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rnk FROM employees WHERE dept_id IS NOT NULL ) SELECT d.dept_name, MAX(r.salary) AS second_highest FROM departments d LEFT JOIN ranked r ON r.dept_id = d.dept_id AND r.rnk = 2 GROUP BY d.dept_id, d.dept_name ORDER BY d.dept_id;4 rows
dept_name second_highest Engineering 80000 Sales 45000 Marketing NULL HR NULL -
Predictsay it before you run it
If Hana's salary were changed to 95000, what would the solution return?
Show answer
70,000. Asha and Hana would both be on 95,000, and DENSE_RANK gives them both rank 1. The next different salary, 70,000 (Ben and Chen), gets rank 2.
sqlUPDATE employees SET salary = 95000 WHERE name = 'Hana'; WITH ranked AS ( SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk FROM employees ) SELECT MAX(salary) AS second_highest FROM ranked WHERE rnk = 2;1 row
second_highest 70000 -
Edge casesSay what happens with NULLs, ties, duplicates or no rows at all, and fix it.
What does your query return on an empty employees ? Make sure it is one row with NULL.
Show answer
One row, with NULL. That is why the solution wraps the result in MAX: an aggregate with no always returns exactly one row, even when no rows match, and MAX of nothing is NULL.
Without the MAX (
SELECT salary FROM ranked WHERE rnk = 2), an empty table would return no rows at all. Below, every employee is deleted first.sqlDELETE FROM employees; WITH ranked AS ( SELECT salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS rnk FROM employees ) SELECT MAX(salary) AS second_highest FROM ranked WHERE rnk = 2;1 row
second_highest NULL
Practice box: write and run your answers here
Your queries in this box
Step 4 Interview-ready, then prove it
Your interview answer
About 20 to 40 seconds, said out loud.
I first confirm that ties should collapse, so the second highest is the second different amount. Then I rank salaries with DENSE_RANK in descending order — dense so a tie doesn't skip a number — and take rank 2. I wrap it in MAX so that if there is no second salary I return a single NULL instead of no rows, and for the Nth highest I just change the 2.
Follow-up questions
How would you return the employees who earn the second highest salary?
Keep the DENSE_RANK with names in it and select every row WHERE rnk = 2.
Why DENSE_RANK and not RANK?
With RANK, a tie at the top makes the next rank 3, so rank 2 would not exist.
Tick each check when you can do it without notes
A topic is finished when you pass all four checks, not when you have read it. Be strict: if you had to look at the notes, it is not ticked yet.
- Explain Say what it does and when to use it, in plain words, with no notes.
- Write Write the query from a blank page, then run it to check.
- Predict Read a query and say which rows it returns before you run it.
- Edge cases Say what happens with NULLs, ties, duplicates or no rows at all, and fix it.
0 of 4 checks ticked.
Saved in this browser. Back it up or move it to another device.