Module 3: Working with Multiple Tables30 min

Self-Joins

Progress Tracking

Log in to save this lesson and continue from where you left off.

Log in

When a Table Needs to Talk to Itself

A self-join is exactly what it sounds like: joining a table to itself. Sounds weird until you see the use cases. Then it makes perfect sense.

The classic example is the employee-manager relationship. Managers are employees too. They all live in the same table. To answer “Who manages whom?”, you need to join employees to employees.

Here’s the pattern — notice how we treat the same table as two different entities using aliases:

SQL
SELECT
  e.name AS employee,
  m.name AS manager
FROM employees e
LEFT JOIN employees m
  ON e.manager_id = m.id;

We’re treating the employees table as two different tables: ‘e’ for employees, ‘m’ for managers. The join connects each employee to their manager. LEFT JOIN because the CEO has no manager.

Aliases are required

Self-joins absolutely require aliases. Without them, SQL can’t tell which “copy” of the table you’re referring to. You’ll get syntax errors.

Comparing Rows Within a Table

Self-joins shine when you need to compare rows to other rows in the same table: “Find employees who earn more than someone else in their department”, “Show me products priced higher than others in their category”.

Table: employees
idfirst_namelast_namesalaryjoining_datedepartment
1001MonikaArora1000002023-01-12HR
1002NiharikaVerma800002023-01-05Admin
1003VishalSinghal3000002023-01-02HR
1004AmitahSingh5000002022-12-15Admin
1005VivekBhati5000002022-11-16Admin
1
Try Row Comparison

Find employees who earn more than a colleague in the same department. Use DISTINCT since one person might outrank several colleagues.

Tables: employees
Inequality joins are expensive

Self-joins with inequality conditions (>, <, <>) are expensive. Without a scope constraint, you’re comparing n×n pairs. On 10,000 employees, that’s 100 million comparisons. Always add a scope like “same department” to keep it manageable.

SQL
-- DANGEROUS: Compares every row to every other row
SELECT *
FROM employees a
JOIN employees b
  ON a.salary > b.salary;
-- 10,000 employees = 100 million comparisons!

-- SAFER: Scope to same department
SELECT *
FROM employees a
JOIN employees b
  ON a.department = b.department
  AND a.salary > b.salary;
-- Much smaller comparison sets

Finding Related Pairs

Another common pattern: finding pairs of related items. Notice how a.id < b.id prevents both duplicate pairs and self-pairs:

Table: employee
idfirst_namelast_nameagesexemployee_titledepartmentsalarytargetbonusemailcityaddressmanager_id
5MaxGeorge26MSalesSales1300200150Max@company.comCalifornia2638 Richards Avenue1
13KattyBond56FManagerManagement1500000300Katty@company.comArizona1
11RicherdGear57MManagerManagement2500000300Richerd@company.comAlabama1
10JenniferDion34FSalesSales1000200150Jennifer@company.comAlabama13
19GeorgeJoe50MManagerManagement1000000300George@company.comFlorida1003 Wyatt Street1
2
Try Pair Finding

Find all pairs of employees in the same department. Use a.id < b.id to avoid duplicates and self-pairs.

Tables: employee

The a.id < b.id condition is important. Without it, you get self-pairs (Alice paired with herself) and duplicate pairs (Alice-Bob and Bob-Alice).

Use < not !=

Use a.id < b.id (not !=) to get each pair exactly once. The inequality ensures you only get pairs where the first ID is smaller.

Table: transportation_numbers
indexnumber
15
23
37
41
50
3
Try Number Pair Products

Find distinct pairs of numbers (from transportation_numbers) where the product exceeds 11. Avoid duplicate pairs.

Tables: transportation_numbers

A Note on Performance

Self-joins can get expensive. Here’s how to keep them under control:

  • Always add a scope predicate (same department, same category, same year)
  • For ranking/comparison tasks, consider window functions instead (Module 6)
  • Test on small data first to understand the row multiplication

Window functions often replace self-joins with cleaner, faster code. But self-joins are still essential for certain patterns, especially hierarchical data.

Employees With the Same Salary

Use a self-join to find employees who earn the same salary as another employee.

Table: worker
worker_idfirst_namelast_namesalaryjoining_datedepartment
1MonikaArora1000002014-02-20HR
2NiharikaVerma800002014-06-11Admin
3VishalSinghal3000002014-02-20HR
4AmitahSingh5000002014-02-20Admin
5VivekBhati5000002014-06-11Admin
4
Employees With the Same Salary
View solution

Find employees who earn the same salary. Output the worker id along with the first name and the salary in descending order.

Tables: worker

Matching Users Pairs

Find matching employee pairs using multiple join conditions on the same table.

Table: facebook_employees
idlocationagegenderis_senior
0USA24MFALSE
1USA31FTRUE
2USA29FFALSE
3USA33MFALSE
4USA36FTRUE
5
Meta/Facebook Matching Users Pairs
View solution

Find matching pairs of Meta/Facebook employees such that they are both of the same nation, different age, same gender, and at different seniority levels. Output ids of paired employees.

Tables: facebook_employees

Key Takeaways

  • Self-joins join a table to itself using different aliases
  • Essential for: hierarchies (org charts), row comparisons, finding pairs
  • Use a.id < b.id to avoid duplicate and self pairs
  • Consider window functions as an alternative for ranking tasks

What’s Next

You’ve now mastered all the join types. Time to put them together: joining three, four, or more tables in a single query. That’s where things get interesting.