SQL interview question: Employees With the Same Salary
Find Employees Who Share a Salary
A pay review wants to see every employee whose salary exactly matches at least one other employee's salary, anywhere in the company.
Return employee_id, name (first and last name with a space), department (the department name), salary and employees_at_salary (how many employees earn that exact salary). Include only employees who share their salary with someone else. Order by salary from highest to lowest, then by employee_id.
Skills: COUNT() OVER (PARTITION BY), GROUP BY with HAVING, and filtering on a window function through a CTE
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 find employees with the same salary, keep the rows whose salary appears more than once: SELECT * FROM employees WHERE salary IN (SELECT salary FROM employees GROUP BY salary HAVING COUNT(*) > 1). If you also want how many people share each salary, use COUNT(*) OVER (PARTITION BY salary) in a CTE and filter on it.
What the question is really asking
There are two readings of "employees with the same salary", so ask which one the interviewer means. One wants a list of people (every employee whose salary matches someone else's). The other wants pairs (who earns the same as whom). In this employees table, 37 people hold 19 different salaries, and 30 of them share a salary with at least one colleague.
The first step for both readings is the same: find the salaries that appear more than once.
SELECT salary, COUNT(*) AS employees
FROM employees
GROUP BY salary
HAVING COUNT(*) > 1
ORDER BY employees DESC, salary DESC;That returns 12 salaries. Two of them, 115000 and 105000, are held by four people each.
Approach 1: IN with GROUP BY and HAVING
GROUP BY collapses the people into one row per salary, so you can't select names from it directly. Wrap it in a subquery instead:
SELECT id, first_name, last_name, salary
FROM employees
WHERE salary IN (
SELECT salary FROM employees GROUP BY salary HAVING COUNT(*) > 1
)
ORDER BY salary DESC, id;This returns the 30 employees. It's the same pattern as finding duplicate emails, applied to a number instead of a string.
Approach 2: a window function
COUNT(*) OVER (PARTITION BY salary) gives every row the size of its salary group without collapsing anything. Because WHERE runs before window functions, the filter has to sit outside a CTE or subquery, which is what the solution above does. The bonus is that the count comes along for free, so you can show that Emily Davis and Christopher Garcia are the two people at 140000. If window functions are new to you, the window functions guide covers PARTITION BY.
Approach 3: EXISTS
You can also ask, row by row, whether anyone else has the same salary:
SELECT COUNT(*)
FROM employees e
WHERE EXISTS (
SELECT 1 FROM employees o
WHERE o.salary = e.salary AND o.id <> e.id
);That counts 30 too. The o.id <> e.id part is what matters: without it, every employee matches themselves and the query returns all 37.
The self join and its duplicates
If the interviewer wants pairs, a self join is the natural tool, and it's easy to get wrong. Joining on salary alone matches everyone with themselves:
SELECT COUNT(*)
FROM employees a
JOIN employees b ON a.salary = b.salary;That gives 89 rows. Adding a.id <> b.id drops the self matches but still lists every pair twice (A with B, then B with A), so you get 52. Use a.id < b.id to keep each pair once:
SELECT a.first_name || ' ' || a.last_name AS employee_1,
b.first_name || ' ' || b.last_name AS employee_2,
a.salary
FROM employees a
JOIN employees b ON a.salary = b.salary AND a.id < b.id
WHERE a.salary = 135000
ORDER BY a.id, b.id;Three people earn 135000 (David Brown, Amanda Rodriguez and Jason Reed), and that gives three pairs. Across the whole table there are 26 pairs. Notice how fast pairs grow: a salary held by four people produces six pairs. That's a good reason to return a list with a count rather than pairs, unless pairs are what was asked for.
Same salary in the same department
A common follow-up limits matches to one department. Add the department to the grouping:
SELECT department_id, salary, COUNT(*) AS employees
FROM employees
GROUP BY department_id, salary
HAVING COUNT(*) > 1;On this data it returns no rows. Every shared salary crosses departments (the two people at 140000 work in Engineering and Sales). An empty result is a correct answer here, and saying so out loud shows you checked the data rather than assumed.
What to say in the interview
Start with the GROUP BY and HAVING subquery, since it's short and works in every dialect. Mention the window version when they want the count on each row, and the a.id < b.id trick when they want pairs. The related Employees Hired on the Same Day question uses the same idea on dates.
Common questions
- How do I find employees with the same salary in SQL?
- Group by salary, keep the groups with
HAVING COUNT(*) > 1, and select the employees whose salary is in that list withWHERE salary IN (...). A window count,COUNT(*) OVER (PARTITION BY salary), does the same job and keeps the count on each row. - Why does my self join return each pair twice?
- Joining on
a.salary = b.salary AND a.id <> b.idmatches A with B and B with A. Usea.id < b.idinstead so each pair appears once. On this sample data that cuts 52 rows down to 26 pairs. - Why can't I use COUNT(*) OVER () in a WHERE clause?
- WHERE is evaluated before window functions are computed, so the count doesn't exist yet. Compute it in a CTE or subquery, then filter in the outer query.
- How do I find employees with the same salary in the same department?
- Group by both columns,
GROUP BY department_id, salary HAVING COUNT(*) > 1, or partition by both in the window version. In this sample data no two people in one department share a salary, so the result is empty. - Do NULL salaries count as the same salary?
- It depends on the approach.
salary IN (...)ando.salary = e.salarynever match NULL, so those rows drop out, butPARTITION BY salaryputs all NULLs in one partition, so the window version would list them as sharing a salary. This table has no NULL salaries, so the result here is the same either way.
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
- SQL Server Use `+` or `CONCAT(e.first_name, ' ', e.last_name)` instead of `||` for the name. The CTE and window function run unchanged.
- MySQL Window functions need MySQL 8.0 or later. On older versions, use the `IN` subquery with `GROUP BY salary HAVING COUNT(*) > 1` and a second subquery for the count.