SQL interview question: Employees Who Are Managers
Find Employees Who Manage Someone
HR wants a list of everyone who has at least one direct report, and the job title isn't a reliable guide to who that is.
Return employee_id, name (first and last name with a space), title and direct_reports (how many employees have this person as their manager_id). Include only employees with at least one direct report. Order by direct_reports from highest to lowest, then by employee_id.
Skills: self joins, GROUP BY on a self join, IN and EXISTS against the same table
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 |
How to solve it
To find employees who are managers, keep the rows whose id appears in another row's manager_id: SELECT * FROM employees WHERE id IN (SELECT manager_id FROM employees). To also count their direct reports, self join with JOIN employees e ON e.manager_id = m.id and GROUP BY m.id.
A manager is defined by the data, not the title
In this employees table, 37 people work here and 35 of them have a manager_id. Those 35 values point at only 12 distinct people, so 12 employees manage someone.
It's tempting to filter on the title instead:
SELECT id, first_name, last_name, title
FROM employees
WHERE title LIKE '%Manager%';That returns 6 people, and 4 of them (Lisa Anderson, Matthew Martin, Michelle Perez and Eric Evans) have no direct reports at all. Meanwhile Melissa Cook, a UX Designer, has one. The relationship is the source of truth, and the label is just text.
Approach 1: IN with a subquery
The shortest answer asks whether each id appears in the manager_id column:
SELECT id, first_name || ' ' || last_name AS name, title
FROM employees
WHERE id IN (SELECT manager_id FROM employees)
ORDER BY id;It returns the 12 managers, from Alice Chen to Melissa Cook. The subquery's list contains NULLs (from the two CEOs, who have no manager), but that's harmless for IN: a NULL in the list can't create a match.
Approach 2: EXISTS
EXISTS asks the same question row by row:
SELECT COUNT(*)
FROM employees e
WHERE EXISTS (
SELECT 1 FROM employees r WHERE r.manager_id = e.id
);This counts 12 as well. Many engines plan IN and EXISTS the same way for a query like this, so pick whichever reads better to you. The EXISTS vs IN question covers where they differ.
Approach 3: a self join, with a catch
A self join is the version that can also count reports, which is why the practice question asks for it. The catch is duplicates. Each manager appears once per person reporting to them, so this join returns 35 rows, not 12:
SELECT COUNT(*)
FROM employees m
JOIN employees e ON e.manager_id = m.id;Add DISTINCT on the manager, or group by the manager, to get back to one row each:
SELECT COUNT(*)
FROM (
SELECT DISTINCT m.id
FROM employees m
JOIN employees e ON e.manager_id = m.id
);That gives 12 again. Grouping is better when you also want the count, as in the solution above: Alice Chen has 6 reports, Robert Martinez and Rachel Phillips have 2, Melissa Cook has 1, and the other eight have 3 each. If the self join itself is new to you, start with Employee and Manager Self Join.
The reverse question and the NOT IN trap
A common follow up is "now find the employees who manage nobody". Flipping IN to NOT IN looks right:
SELECT COUNT(*)
FROM employees
WHERE id NOT IN (SELECT manager_id FROM employees);It returns 0. The subquery's list includes NULL, and id NOT IN (..., NULL) is never true, because the database can't rule out that the unknown value equals the id. Filter the NULLs out, or use NOT EXISTS:
SELECT COUNT(*)
FROM employees e
WHERE NOT EXISTS (
SELECT 1 FROM employees r WHERE r.manager_id = e.id
);Both fixes count 25, which matches 37 employees minus 12 managers. Don't confuse this with Employees Without a Manager, which looks at the other end of the relationship: people whose own manager_id is NULL.
What to say in the interview
Start by defining a manager as "someone whose id appears in manager_id", then write the IN version. Mention that a self join needs DISTINCT or GROUP BY, and offer the grouped version if they want report counts. If they ask for the opposite list, explain why NOT IN fails with NULLs before you're asked.
Common questions
- How do you find employees who are also managers in SQL?
- Check whether each employee's id appears in the manager_id column of the same table, with
WHERE id IN (SELECT manager_id FROM employees)or a correlatedEXISTS. Both return each manager once. - Why does my self join return the same manager several times?
- A join produces one row per match, so a manager with 6 reports appears 6 times. Use
SELECT DISTINCTon the manager columns, orGROUP BYthe manager's id if you also wantCOUNT(*)of their reports. - Why does NOT IN return no rows when finding employees who are not managers?
- The manager_id column contains NULL for anyone without a manager, and
x NOT IN (..., NULL)never evaluates to true. AddWHERE manager_id IS NOT NULLinside the subquery or switch toNOT EXISTS. - How do you list managers with more than a certain number of direct reports?
- Group the self join by the manager and filter with HAVING, for example
HAVING COUNT(e.id) >= 3. On this data that keeps 9 of the 12 managers.
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 Concatenate the name with `+` or `CONCAT(m.first_name, ' ', m.last_name)` instead of `||`. The rest of the query runs unchanged.