SQL interview question: Nth Highest Salary

Find the Nth Highest Salary

Several people at the company share the same salary, so a plain sort will count the same amount more than once.

Find every employee who earns the 6th highest distinct salary. Return first_name, last_name and salary.

Skills: DENSE_RANK, DISTINCT and LIMIT with OFFSET

Tables

employees 37 rows
  • id integer PK
  • first_name text
  • last_name text
  • email text
  • department_id integer → departments
  • salary integer
  • hire_date text
  • manager_id integer → employees
  • title text
idfirst_namelast_nameemaildepartment_idsalaryhire_datemanager_idtitle
1AliceChen[email protected]12500002015-01-15NULLCEO
2RobertMartinez[email protected]21800002016-03-20NULLCEO
3SarahJohnson[email protected]11500002017-06-101VP Engineering

How to solve it

To find the Nth highest salary, rank the distinct salaries with DENSE_RANK() OVER (ORDER BY salary DESC) and keep the rows where the rank equals N. If you only need the amount, SELECT DISTINCT salary ... ORDER BY salary DESC LIMIT 1 OFFSET N - 1 also works.

Why the plain LIMIT trick breaks

The first query most people write is:

SELECT salary FROM employees
ORDER BY salary DESC
LIMIT 1 OFFSET 5;

It skips five rows and returns the sixth. That is only right when every salary is different. In this table two people earn 140000 and three earn 135000, so skipping five rows lands on the second 140000 and returns the 5th highest amount, not the 6th. Interviewers pick data like this on purpose.

Three ways to get it right

1. DISTINCT with LIMIT and OFFSET. Remove the duplicates first, then skip N - 1 amounts:

SELECT DISTINCT salary FROM employees
ORDER BY salary DESC
LIMIT 1 OFFSET 5;

This returns the amount only. It is short and easy to explain, which makes it a good first answer.

2. DENSE_RANK. Rank the amounts so ties share a rank, then filter:

WITH ranked AS (
  SELECT first_name, last_name, salary,
         DENSE_RANK() OVER (ORDER BY salary DESC) AS salary_rank
  FROM employees
)
SELECT first_name, last_name, salary
FROM ranked
WHERE salary_rank = 6;

This is the answer most interviewers want to see, because it also returns the people and handles ties without extra work. Swap ORDER BY salary DESC for ORDER BY salary to get the Nth lowest instead.

3. A correlated subquery. Keep the salary that has exactly N - 1 distinct amounts above it:

SELECT DISTINCT salary FROM employees e1
WHERE 5 = (SELECT COUNT(DISTINCT salary)
           FROM employees e2
           WHERE e2.salary > e1.salary);

It works in databases without window functions, but it compares every row with every other row, so mention that it gets slow on big tables.

RANK, DENSE_RANK or ROW_NUMBER?

  • DENSE_RANK gives ties the same rank and leaves no gaps (1, 2, 2, 3). Rank N is always the Nth distinct amount, which is what this question asks for.
  • RANK gives ties the same rank but then skips numbers (1, 2, 2, 4). Rank 3 may not exist at all.
  • ROW_NUMBER gives every row its own number, so it behaves like the broken LIMIT query above.

What to say in the interview

Ask two questions before writing anything: does "Nth highest" mean distinct amounts, and what should come back when fewer than N distinct salaries exist? Then write the DENSE_RANK version, mention the LIMIT and OFFSET shortcut, and explain why ROW_NUMBER would be wrong. For the per-department version of this question, add PARTITION BY department_id inside the OVER clause.

Common questions

What is the difference between DENSE_RANK and RANK for the Nth highest salary?
DENSE_RANK numbers tied salaries the same and continues with the next number, so rank N is always the Nth distinct salary. RANK skips numbers after a tie, so the rank you are looking for might not exist.
How do I return NULL when there is no Nth highest salary?
Wrap the query in a scalar subquery, for example SELECT (SELECT DISTINCT salary FROM employees ORDER BY salary DESC LIMIT 1 OFFSET 5) AS nth_salary. When fewer than six distinct salaries exist, the subquery returns no row and the outer SELECT shows NULL.
How do I find the Nth highest salary in each department?
Add PARTITION BY to the window: DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC). Then filter on the rank as before, and you get one result per department.
Does LIMIT and OFFSET work in SQL Server?
SQL Server uses OFFSET 5 ROWS FETCH NEXT 1 ROWS ONLY after the ORDER BY instead of LIMIT. The DENSE_RANK version works unchanged in SQL Server, PostgreSQL, MySQL 8 and SQLite.

Practice on your iPhone

Query Dojo for iPhone has 85 interview questions, timed mock interviews and a tile builder, so you can write queries without a keyboard.

Get the app

Other SQL dialects

  • SQL Server Use OFFSET 5 ROWS FETCH NEXT 1 ROWS ONLY instead of LIMIT 1 OFFSET 5. DENSE_RANK works the same way.
  • MySQL DENSE_RANK needs MySQL 8.0 or newer. On 5.7, use the subquery with SELECT DISTINCT and LIMIT 1 OFFSET 5.

Keep going