SQL Quest
Sign in Français 0 XP Lvl 1
Intermediate

SQL self join: join a table with itself

4 min read · 3 runnable examples · updated October 9, 2026

Sometimes the relationship lives inside a single table. In the employes (employees) table, the manager_id column holds the identifier of… another employee. To show “Léa reports to Hugo”, you need to join the employes table with itself: that is a self join.

Syntax

SELECT e.nom, m.nom AS manager
FROM employes e
LEFT JOIN employes m ON m.id = e.manager_id;

How it works

A self join has no special keyword: it is a regular JOIN where the same table appears twice. To tell them apart, aliases are required: here e is the employee and m their manager.

A LEFT JOIN is often used, to keep the rows that have no “parent” (the CEO has no manager).

Self joins are also used to compare rows with each other: employees who earn more than their manager, products in the same category, customers from the same city…

Diagram

employes
idnommanager_id
1DurandNULL
2Martin1
3Roux2
result
employeemanager
DurandNULL
MartinDurand
RouxMartin
employes e LEFT JOIN employes m: each employee finds their manager's row in the same table.

Simplified diagram to show the idea. The examples below use the real SQL Quest practice database (a fictional online shop: see the full schema).

Glossary: the sample database uses French names
clients
customers
commandes
orders
lignes_commande
order lines
produits
products
fournisseurs
suppliers
employes
employees
prenom / nom
first name / last name (or name)
ville
city
pays
country
prix
price
prix_unitaire
unit price
quantite
quantity
categorie
category
date_commande
order date
poste
job title
departement
department
salaire
salary
statut
status: livrée = delivered, expédiée = shipped, en cours = in progress, annulée = cancelled

Examples with their results

1. Each employee and their manager

The CEO appears with a NULL manager thanks to the LEFT JOIN.

SELECT e.prenom || ' ' || e.nom AS employe, e.poste,
       m.prenom || ' ' || m.nom AS manager
FROM employes e
LEFT JOIN employes m ON m.id = e.manager_id
LIMIT 8;
Try it in the sandbox
Result
employepostemanager
Chloé DuboisPDGNULL
Jade LefebvreDirecteur commercialChloé Dubois
Nathan RouxDirectrice techniqueChloé Dubois
Samir SimonResponsable RHChloé Dubois
Alice SimonCommercialJade Lefebvre
Nathan MartinCommercialJade Lefebvre
Lucas MoreauCommercialJade Lefebvre
Emma LambertCommercialJade Lefebvre

8 rows in total

2. Count the direct reports

How many people does each manager lead directly?

SELECT m.prenom, m.nom, m.poste, COUNT(e.id) AS equipe
FROM employes m
JOIN employes e ON e.manager_id = m.id
GROUP BY m.id
ORDER BY equipe DESC;
Try it in the sandbox
Result
prenomnomposteequipe
JadeLefebvreDirecteur commercial12
SamirSimonResponsable RH5
NathanRouxDirectrice technique4
ChloéDuboisPDG3

4 rows in total

3. Compare two rows

The employees who earn more than half of their manager's salary.

SELECT e.nom, e.salaire, m.nom AS manager, m.salaire AS salaire_manager
FROM employes e
JOIN employes m ON m.id = e.manager_id
WHERE e.salaire > m.salaire / 2
LIMIT 8;
Try it in the sandbox
Result
nomsalairemanagersalaire_manager
Lefebvre6,200Dubois9,500
Roux6,800Dubois9,500
Simon4,800Dubois9,500
Simon4,800Lefebvre6,200
Martin3,400Lefebvre6,200
Moreau4,600Lefebvre6,200
Lambert4,200Lefebvre6,200
Lefebvre3,800Lefebvre6,200

8 rows in total

Common mistakes

Forgetting the aliases

Without aliases, SQL cannot tell which “copy” of the table each column belongs to.

Avoid
SELECT nom, nom FROM employes JOIN employes ON id = manager_id;
Correct
SELECT e.nom, m.nom FROM employes e JOIN employes m ON m.id = e.manager_id;

Reversing the condition

e.id = m.manager_id gives the direct reports instead of the managers. Read the condition again: the manager's id must equal the employee's manager_id.

Avoid
SELECT e.nom, m.nom AS manager FROM employes e JOIN employes m ON e.id = m.manager_id;
Correct
SELECT e.nom, m.nom AS manager FROM employes e JOIN employes m ON m.id = e.manager_id;

Losing the top of the hierarchy

With an INNER JOIN, the employee without a manager disappears. Use LEFT JOIN to keep them.

Avoid
SELECT e.nom, m.nom FROM employes e JOIN employes m ON m.id = e.manager_id;
Correct
SELECT e.nom, m.nom FROM employes e LEFT JOIN employes m ON m.id = e.manager_id;

Exercises

Try writing the query yourself before opening the solution.

Challenge 1. Show the employees (first name, last name) whose manager is the “Directeur commercial” (sales director).

Show a solution
SELECT e.prenom, e.nom FROM employes e JOIN employes m ON m.id = e.manager_id WHERE m.poste = 'Directeur commercial';
prenomnom
AliceSimon
NathanMartin
LucasMoreau
EmmaLambert
AliceLefebvre
InèsMercier
LouisBernard
LouisGarcia

12 rows in total · 8 shown

Try it in the sandbox

Challenge 2. Show each manager and the average salary of their team.

Show a solution
SELECT m.nom, ROUND(AVG(e.salaire)) AS salaire_moyen_equipe FROM employes m JOIN employes e ON e.manager_id = m.id GROUP BY m.id;
nomsalaire_moyen_equipe
Dubois5,933
Lefebvre4,417
Roux4,250
Simon4,120

4 rows in total

Try it in the sandbox
Want more practice?

The interactive exercises are checked automatically, with a tutor who explains your mistakes. A short lesson stays next to the editor.

Do the interactive exercises

Frequently asked questions

Is there a SELF JOIN keyword?

No. A self join is written with an ordinary JOIN where the same table appears twice, with two different aliases.

How do I show the whole multi-level hierarchy?

With a recursive CTE (WITH RECURSIVE), which repeats the self join level by level.

Is a self join slow?

No more than a join between two different tables. An index on the linking column (here manager_id) speeds it up on large tables.

Read next