All 40 lessons
Lesson 19 of 40 · Joins
SQL SELF JOIN
By Skillancy Editorial Team
Every example runs on the ShopKart dataset, already loaded in the free SQL Compiler. Click Try it in the SQL Compiler under a query to open it there, then press Run.
A self join joins a table to itself. You give the table two aliases and treat them as if they were two separate tables. It's how you answer questions that compare rows within one table, like "who is each employee's manager?"
Syntax
SELECT a.column, b.column
FROM table_name a
JOIN table_name b
ON a.some_column = b.other_column;There's no special keyword: it's an ordinary JOIN (or LEFT JOIN) where both sides are the same table.
Examples
1. Employees and their managers
The employees table stores each person's manager_id, which is another employee's employee_id:
SELECT e.employee_name AS employee,
e.department,
m.employee_name AS manager
FROM employees e
JOIN employees m ON e.manager_id = m.employee_id
ORDER BY m.employee_name, e.employee_name;Try it in the SQL CompilerResult:
| employee | department | manager |
|---|---|---|
| Manish Tiwari | Technology | Amit Verma |
| Ritika Bose | Technology | Amit Verma |
| Deepak Yadav | Technology | Manish Tiwari |
| Kavya Pillai | Operations | Neha Joshi |
| Suresh Rao | Operations | Neha Joshi |
| Sana Qureshi | Marketing | Pooja Sinha |
| Tanvi Shah | Marketing | Pooja Sinha |
| Amit Verma | Technology | Rajesh Khanna |
| Neha Joshi | Operations | Rajesh Khanna |
| Pooja Sinha | Marketing | Rajesh Khanna |
| Harish Gowda | Operations | Suresh Rao |
(11 rows)
Read e as "the employee row" and m as "the manager row". The CEO, Rajesh Khanna, is missing because his manager_id is NULL.
2. Keep the top of the hierarchy with LEFT JOIN
SELECT e.employee_name AS employee,
COALESCE(m.employee_name, '— (top of org)') AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.employee_id
ORDER BY e.employee_id
LIMIT 5;Try it in the SQL CompilerResult:
| employee | manager |
|---|---|
| Rajesh Khanna | — (top of org) |
| Neha Joshi | Rajesh Khanna |
| Amit Verma | Rajesh Khanna |
| Pooja Sinha | Rajesh Khanna |
| Suresh Rao | Neha Joshi |
(5 rows)
3. Employees who earn more than their manager
A classic interview question:
SELECT e.employee_name, e.salary, m.employee_name AS manager, m.salary AS manager_salary
FROM employees e
JOIN employees m ON e.manager_id = m.employee_id
WHERE e.salary > m.salary;Try it in the SQL CompilerResult:
(0 rows)
Nobody at ShopKart does. A softer version: employees earning more than 55% of their manager's pay.
SELECT e.employee_name,
e.salary,
m.employee_name AS manager,
m.salary AS manager_salary,
ROUND(e.salary / m.salary * 100) AS pct_of_manager
FROM employees e
JOIN employees m ON e.manager_id = m.employee_id
WHERE e.salary > 0.55 * m.salary;Try it in the SQL CompilerResult:
| employee_name | salary | manager | manager_salary | pct_of_manager |
|---|---|---|---|---|
| Amit Verma | 260000.00 | Rajesh Khanna | 450000.00 | 58 |
| Manish Tiwari | 150000.00 | Amit Verma | 260000.00 | 58 |
| Ritika Bose | 150000.00 | Amit Verma | 260000.00 | 58 |
(3 rows)
4. Team size per manager
SELECT m.employee_name AS manager,
COUNT(e.employee_id) AS direct_reports
FROM employees m
JOIN employees e ON e.manager_id = m.employee_id
GROUP BY m.employee_name
ORDER BY direct_reports DESC, manager;Try it in the SQL CompilerResult:
| manager | direct_reports |
|---|---|
| Rajesh Khanna | 3 |
| Amit Verma | 2 |
| Neha Joshi | 2 |
| Pooja Sinha | 2 |
| Manish Tiwari | 1 |
| Suresh Rao | 1 |
(6 rows)
5. Pairs within the same group
Pairs of customers who live in the same city, each pair shown once:
SELECT a.customer_name AS customer_1,
b.customer_name AS customer_2,
a.city
FROM customers a
JOIN customers b
ON a.city = b.city
AND a.customer_id < b.customer_id;Try it in the SQL CompilerResult:
| customer_1 | customer_2 | city |
|---|---|---|
| Aarav Sharma | Divya Menon | Bengaluru |
(1 row)
a.customer_id < b.customer_id stops a customer pairing with themselves and avoids listing both (A, B) and (B, A).
Common mistakes
- Using the same alias twice, or no aliases. Each copy of the table needs its own name.
- Mixing up direction.
ON e.manager_id = m.employee_idfinds each employee's manager. Swapping the columns finds each manager's reports, so name aliases clearly. - Using
<>instead of<for pairs, which returns every pair twice.
Practice question
Find pairs of employees in the same department who earn exactly the same salary. Show each pair once.
Show solution
SELECT a.employee_name AS employee_1,
b.employee_name AS employee_2,
a.department,
a.salary
FROM employees a
JOIN employees b
ON a.department = b.department
AND a.salary = b.salary
AND a.employee_id < b.employee_id;Try it in the SQL CompilerResult:
| employee_1 | employee_2 | department | salary |
|---|---|---|---|
| Manish Tiwari | Ritika Bose | Technology | 150000.00 |
(1 row)
Key takeaways
- A self join is a normal join with the same table on both sides.
- Two aliases (such as
eandm) make the roles clear. - Use
LEFT JOINto keep rows with no match, like the top of a hierarchy. a.id < b.idreturns each pair once. For multi-level hierarchies, use a recursive CTE.
Frequently asked questions
What is a self join in SQL?
A self join joins a table to itself using two different aliases, so you can compare rows of the same table, such as an employee and their manager.
Is SELF JOIN a keyword?
No. A self join is a regular INNER or LEFT JOIN where both sides are the same table. There is no SELF JOIN keyword.
Why use a.id < b.id in a self join?
It stops a row pairing with itself and returns each pair only once, instead of both (A, B) and (B, A).
Keep learning SQL
One email a week with SQL and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.
Prefer a one-page summary? Download the free SQL cheat sheet (PDF)