A clear SQL solution for finding employees whose salary is greater than their manager's salary using a self join.
Problem Restatement
We are given a table named Employee.
| Column | Type |
|---|---|
| id | int |
| name | varchar |
| salary | int |
| managerId | int |
Each row stores one employee. The managerId column points to the id of that employee’s manager.
We need to return the names of employees who earn more than their managers. The result can be returned in any order. The official problem asks for employees whose salary is greater than their manager’s salary.
Input and Output
| Item | Meaning |
|---|---|
| Input | Table Employee(id, name, salary, managerId) |
| Output | One column named Employee |
| Rule | Employee salary must be greater than manager salary |
| Order | Any order is accepted |
Expected output column:
EmployeeExamples
Input:
Employee
+----+-------+--------+-----------+
| id | name | salary | managerId |
+----+-------+--------+-----------+
| 1 | Joe | 70000 | 3 |
| 2 | Henry | 80000 | 4 |
| 3 | Sam | 60000 | null |
| 4 | Max | 90000 | null |
+----+-------+--------+-----------+Joe’s manager is Sam.
Joe salary = 70000
Sam salary = 60000Joe earns more than Sam, so Joe should appear in the result.
Henry’s manager is Max.
Henry salary = 80000
Max salary = 90000Henry does not earn more than Max, so Henry should not appear.
Output:
+----------+
| Employee |
+----------+
| Joe |
+----------+First Thought: Compare Employees With Their Managers
The table contains both employees and managers.
A manager is also an employee row.
So we need to read the same table in two roles:
| Alias | Meaning |
|---|---|
e | The employee |
m | The manager |
Then we connect them with:
e.managerId = m.idAfter that, we keep only rows where:
e.salary > m.salaryKey Insight
This is a self join problem.
A self join means joining a table with itself. We use it when rows in the same table refer to other rows in that same table.
Here, Employee.managerId refers back to Employee.id.
So the join gives us one row containing both:
| Value | Comes from |
|---|---|
| Employee name | e.name |
| Employee salary | e.salary |
| Manager name | m.name |
| Manager salary | m.salary |
Then the comparison is direct.
Algorithm
- Start from
Employeease. - Join
Employeeagain asm. - Match each employee to their manager using
e.managerId = m.id. - Filter rows where
e.salary > m.salary. - Return
e.nameasEmployee.
Correctness
The join condition:
e.managerId = m.idpairs every employee with their actual manager.
After this join, e.salary is the employee’s salary, and m.salary is that employee’s manager’s salary.
The WHERE condition:
e.salary > m.salarykeeps exactly the employees who earn more than their managers.
Employees without managers have managerId = null. They do not join with a manager row, so they are naturally excluded.
Therefore, every returned name satisfies the problem condition, and every employee satisfying the condition is returned.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) expected | The join can use the primary key index on id |
| Space | O(k) | The result stores qualifying employees |
Here, n is the number of employees, and k is the number of employees returned.
Implementation
SELECT
e.name AS Employee
FROM Employee e
JOIN Employee m
ON e.managerId = m.id
WHERE e.salary > m.salary;Code Explanation
This gives the employee table the alias e:
FROM Employee eThis joins the same table again as m, meaning manager:
JOIN Employee m
ON e.managerId = m.idNow each employee row is connected to its manager row.
This condition keeps only employees who earn more:
WHERE e.salary > m.salaryFinally, this returns the employee name under the required output column:
SELECT
e.name AS EmployeeAlternative Implementation
A LEFT JOIN also works, but it gives no benefit here because employees without managers cannot satisfy the salary comparison.
SELECT
e.name AS Employee
FROM Employee e
LEFT JOIN Employee m
ON e.managerId = m.id
WHERE e.salary > m.salary;The WHERE condition removes rows where the manager is missing, because e.salary > null evaluates to unknown.
For this problem, the inner join version is cleaner.
Testing
Test case 1:
CREATE TABLE Employee (
id INT PRIMARY KEY,
name VARCHAR(255),
salary INT,
managerId INT
);
INSERT INTO Employee (id, name, salary, managerId) VALUES
(1, 'Joe', 70000, 3),
(2, 'Henry', 80000, 4),
(3, 'Sam', 60000, NULL),
(4, 'Max', 90000, NULL);
SELECT
e.name AS Employee
FROM Employee e
JOIN Employee m
ON e.managerId = m.id
WHERE e.salary > m.salary;Expected:
+----------+
| Employee |
+----------+
| Joe |
+----------+Test case 2:
DELETE FROM Employee;
INSERT INTO Employee (id, name, salary, managerId) VALUES
(1, 'Alice', 50000, NULL);Expected:
+----------+
| Employee |
+----------+There is no manager to compare with.
Test case 3:
DELETE FROM Employee;
INSERT INTO Employee (id, name, salary, managerId) VALUES
(1, 'Alice', 100000, 2),
(2, 'Bob', 90000, NULL);Expected:
+----------+
| Employee |
+----------+
| Alice |
+----------+Test case 4:
DELETE FROM Employee;
INSERT INTO Employee (id, name, salary, managerId) VALUES
(1, 'Alice', 70000, 2),
(2, 'Bob', 70000, NULL);Expected:
+----------+
| Employee |
+----------+Equal salary does not count.