SQL interview question: Highest Salary in Each Department
Find the Highest Salary in Each Department
HR wants to know who the top earner is in every department, not just the top amount.
Return dept_name, first_name, last_name and salary for the highest-paid employee in each department. If two people in a department tie for the top salary, return both. Order by dept_name.
Skills: GROUP BY with MAX, joins and RANK with PARTITION BY
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
| id | first_name | last_name | department_id | salary | hire_date | manager_id | title | |
|---|---|---|---|---|---|---|---|---|
| 1 | Alice | Chen | [email protected] | 1 | 250000 | 2015-01-15 | NULL | CEO |
| 2 | Robert | Martinez | [email protected] | 2 | 180000 | 2016-03-20 | NULL | CEO |
| 3 | Sarah | Johnson | [email protected] | 1 | 150000 | 2017-06-10 | 1 | VP Engineering |
departments 5 rows
- id integer PK
- name text
- location text
| id | name | location |
|---|---|---|
| 1 | Engineering | San Francisco |
| 2 | Sales | New York |
| 3 | Marketing | Los Angeles |
How to solve it
To get the highest salary in each department, use SELECT department_id, MAX(salary) FROM employees GROUP BY department_id. To also return who earns it, rank inside each department with RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) and keep rank 1, or join back to that GROUP BY result on both department and salary.
The amount is easy, the person is the real question
If you only need the number, GROUP BY with MAX is the whole answer:
SELECT department_id, MAX(salary) AS top_salary
FROM employees
GROUP BY department_id;On this data that gives five rows, from 250000 in department 1 (Engineering) down to 120000 in department 4 (HR). The usual follow-up is "and who earns it?", and that's where answers start to go wrong.
The query that looks right but isn't
A common first try filters on the list of top salaries:
SELECT first_name, last_name, department_id, salary
FROM employees
WHERE salary IN (SELECT MAX(salary) FROM employees GROUP BY department_id);It returns 10 rows instead of 5. The top salary in Finance is 135000, and David Brown in Engineering earns 135000 too, so he slips in even though he's nowhere near the top of his own department. Lisa Anderson (130000), Jennifer Thomas (120000), Amanda Rodriguez (135000) and Nicole Martinez (120000) get in the same way. The subquery lost the link between a salary and its department.
Another shortcut is putting first_name next to MAX(salary) without grouping by it. SQLite allows that and fills the name from the row with the maximum, but PostgreSQL and SQL Server reject the query, and MySQL rejects it when ONLY_FULL_GROUP_BY is on (the default since 5.7). Even in SQLite, a tie at the top gives you only one of the tied people.
Three correct ways
1. Join back to the grouped maximum. Compute the top salary per department, then join on both columns:
SELECT d.name AS dept_name, e.first_name, e.last_name, e.salary
FROM employees e
JOIN departments d ON d.id = e.department_id
JOIN (SELECT department_id, MAX(salary) AS top_salary
FROM employees
GROUP BY department_id) m
ON m.department_id = e.department_id
AND m.top_salary = e.salary
ORDER BY dept_name;This runs on every database, including MySQL 5.7, and keeps ties.
2. Correlated subquery. Keep each employee whose salary equals the maximum of their own department:
SELECT first_name, last_name, department_id, salary
FROM employees e
WHERE salary = (SELECT MAX(salary)
FROM employees x
WHERE x.department_id = e.department_id);It reads almost like the question itself. The inner query depends on the outer row, so mention that it can get slow on large tables without an index on department_id.
3. RANK with PARTITION BY. This is the reference solution above. PARTITION BY restarts the ranking in each department, and rank 1 is the top salary. It's the easiest one to change: rank 2 gives the runner-up, and salary_rank <= 3 gives a top 3.
All three return the same five people here: Alice Chen (Engineering, 250000), Jason Reed (Finance, 135000), Steven Collins (HR, 120000), Kevin Turner (Marketing, 130000) and Robert Martinez (Sales, 180000).
RANK or ROW_NUMBER?
No department in this data has a tie at the top, so ROW_NUMBER would return the same rows today. The difference shows up when two people share the top salary: RANK gives both of them rank 1, while ROW_NUMBER picks one and drops the other without any warning. Use ROW_NUMBER only when the question asks for exactly one row per group, and then add a tie breaker such as ORDER BY salary DESC, id. The RANK vs DENSE_RANK question covers the ranking functions side by side.
What to say in the interview
Ask first whether ties should return everyone, and whether departments with no employees should appear. Then write the RANK version, and mention the join to GROUP BY as the option for databases without window functions. If they push further, explain why salary IN (...) is wrong, using a salary that exists in two departments as the example. For the next step up, try Top 3 Salaries per Department or the Nth Highest Salary.
Common questions
- Why can't I select the employee name with MAX(salary) and GROUP BY?
- Most databases reject a column in the SELECT that is neither grouped nor aggregated, because a group has many names and only one maximum. SQLite and older MySQL settings allow it, but you can't rely on it, and it returns only one person when there's a tie.
- How do I get the second highest salary in each department?
- Use
DENSE_RANK() OVER (PARTITION BY department_id ORDER BY salary DESC)and keep rank 2. On this data that returns Sarah Johnson at 150000 in Engineering and Christopher Garcia at 140000 in Sales, among others. - Should I use RANK, DENSE_RANK or ROW_NUMBER for the top earner per group?
- For rank 1, RANK and DENSE_RANK give the same result and both keep ties. ROW_NUMBER returns exactly one row per group and drops tied employees, so use it only when the question asks for one row.
- How do I show the department name instead of the department id?
- Join employees to departments on
departments.id = employees.department_idand selectdepartments.name. Do the join before ranking or inside the grouped query, as in the reference solution.
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.
Other SQL dialects
- MySQL RANK and other window functions need MySQL 8.0 or newer. On 5.7, use the join to a GROUP BY subquery or the correlated subquery.
- SQL Server The RANK version works as written. SQL Server rejects selecting first_name next to MAX(salary) without grouping by it, so the bare column shortcut is not an option there.