SQL Tutorial › Joins & Set Operators › SELF JOIN

SQL SELF JOIN

IntermediateLesson 36 of 699 min read6 queries run on MySQL 8.4

A self join joins a table to itself. It is how you answer questions about rows that point to other rows of the same table: an employee and their manager, two customers in the same city, a product and a cheaper one in its category. The only new idea is that both copies of the table need different aliases.

Advertisement
In this lesson
  • Join a table to itself with two aliases
  • List employees with their manager
  • Keep the top of the hierarchy with LEFT JOIN
  • Compare and pair rows of the same table

Sample data

manager_id stores the emp_id of each employee’s manager, another row in the same table:

employees · 12 rows (some columns)
emp_idfirst_namejob_titlemanager_id
100MeeraCEONULL
101AshaEngineering Manager100
102RahulSenior Developer101
103PriyaQA Engineer101
104KaranDeveloper102
105VikramSales Manager100
106NehaSales Executive105
107ArjunSales Executive105
108KavyaMarketing Lead100
109RohanContent Writer108
110SnehaHR Manager100
111AmitIntern101

Employees and their managers

SQL
SELECT e.emp_id, e.first_name AS employee, m.first_name AS managerFROM employees eJOIN employees m ON m.emp_id = e.manager_idORDER BY e.emp_id;
ResultMySQL 8.4
emp_idemployeemanager
101AshaMeera
102RahulAsha
103PriyaAsha
104KaranRahul
105VikramMeera
106NehaVikram
107ArjunVikram
108KavyaMeera
109RohanKavya
110SnehaMeera
111AmitAsha
11 rows in set
How this query works, step by step
  1. employees e is the copy that plays the employee; employees m is the copy that plays the manager.
  2. ON m.emp_id = e.manager_id finds, for each employee, the row whose id equals their manager_id.
  3. The CEO’s manager_id is NULL, so she has no match and the inner join drops her: 11 rows.

Keep the CEO with LEFT JOIN

SQL
SELECT e.first_name AS employee,       COALESCE(m.first_name, '(no manager)') AS managerFROM employees eLEFT JOIN employees m ON m.emp_id = e.manager_idORDER BY e.emp_id;
ResultMySQL 8.4
employeemanager
Meera(no manager)
AshaMeera
RahulAsha
PriyaAsha
KaranRahul
VikramMeera
NehaVikram
ArjunVikram
KavyaMeera
RohanKavya
SnehaMeera
AmitAsha
12 rows in set
Advertisement

Count direct reports

Group by the manager copy to see how many people report to each manager:

SQL
SELECT m.emp_id, m.first_name AS manager, COUNT(*) AS direct_reportsFROM employees eJOIN employees m ON m.emp_id = e.manager_idGROUP BY m.emp_id, m.first_nameORDER BY direct_reports DESC, m.emp_id;
ResultMySQL 8.4
emp_idmanagerdirect_reports
100Meera4
101Asha3
105Vikram2
102Rahul1
108Kavya1
5 rows in set
How this query works, step by step
  1. Each row of the join is one employee-manager pair.
  2. Grouping by the manager counts their pairs.
  3. Meera has 4 direct reports and Asha 3. People who manage nobody do not appear.

Employees who earn more than their manager

A classic interview question:

SQL
SELECT e.first_name AS employee, e.salary, m.first_name AS manager, m.salary AS manager_salaryFROM employees eJOIN employees m ON m.emp_id = e.manager_idWHERE e.salary > m.salary;
ResultMySQL 8.4
Empty set

In the sample data nobody does, so the result is an Empty set. Change the condition to e.salary > m.salary * 0.75 to find people paid close to their manager.

Advertisement

Pairs of rows

Employees who work in the same city, each pair listed once:

SQL
SELECT a.first_name AS person_1, b.first_name AS person_2, a.cityFROM employees aJOIN employees b ON b.city = a.city AND a.emp_id < b.emp_idWHERE a.city = 'Pune'ORDER BY a.emp_id, b.emp_id;
ResultMySQL 8.4
person_1person_2city
PriyaKavyaPune
PriyaRohanPune
KavyaRohanPune
3 rows in set
How this query works, step by step
  1. b.city = a.city pairs people from the same city.
  2. a.emp_id < b.emp_id removes pairs of a person with themselves and keeps only one order of each pair (Priya-Kavya, not also Kavya-Priya).
  3. Pune has three employees, so there are 3 pairs.

Two levels up

Chain the self join to reach the manager’s manager:

SQL
SELECT e.first_name AS employee, m.first_name AS manager, g.first_name AS managers_managerFROM employees eJOIN employees m ON m.emp_id = e.manager_idJOIN employees g ON g.emp_id = m.manager_idORDER BY e.emp_id;
ResultMySQL 8.4
employeemanagermanagers_manager
RahulAshaMeera
PriyaAshaMeera
KaranRahulAsha
NehaVikramMeera
ArjunVikramMeera
RohanKavyaMeera
AmitAshaMeera
7 rows in set

For any depth, use a recursive CTE.

Advertisement

Exercise: Same-price products

List pairs of different products in the same category where the first is cheaper than the second, for the Stationery and Electronics categories.

Expected resultMySQL 8.4
categorycheaperdearer
ElectronicsWireless MouseUSB-C Hub
ElectronicsWireless MouseMechanical Keyboard
ElectronicsUSB-C HubMechanical Keyboard
StationeryGel PensNotebook Pack
4 rows in set
Hint 1

Join products a to products b on category, with a.price < b.price.

Quick quiz

1. Why does a self join need two aliases?

2. Why is the CEO missing from employees e JOIN employees m ON m.emp_id = e.manager_id?

3. What does a.emp_id < b.emp_id do in a pairs query?

Advertisement

SELF JOIN FAQ

What is a self join in SQL?

A self join is a normal join (inner or outer) in which a table is joined to itself, using two different aliases.

When do you use a self join?

For hierarchies such as employees and managers, for comparing rows within the same table, and for finding pairs or duplicates.

Is SELF JOIN a keyword?

No. You write JOIN or LEFT JOIN with the same table twice.