Intermediate: combining and changing data · JOINs · topic 24 of 62 · 30 min
Self joins and cross joins
Explanation
Why join a table to itself?
In COMPANY, manager_id points to another row of the same employees table. Rahul's manager_id is 1, which is Meera's id. To show "Rahul Sharma — Meera Iyer" you need two copies of employees: one playing the employee, one playing the manager. SQL does not copy anything; you simply name the table twice with two different aliases.
SELECT e.name AS employee, m.name AS manager
FROM employees AS e
LEFT JOIN employees AS m ON m.id = e.manager_id;
-- Meera Iyer | NULL <- the boss has no manager
-- Rahul Sharma | Meera Iyer
-- Anita Desai | Rahul Sharma
-- ... 11 rowsThe aliases are required here. Without them, employees.name would be ambiguous: which copy do you mean? Choose aliases that say the role, like e and m, or emp and mgr.
Comparing a row with its partner
Once both copies are side by side you can compare them. A classic interview question is "find employees who earn more than their manager".
SELECT e.name, e.salary, m.name AS manager, m.salary AS manager_salary
FROM employees AS e
JOIN employees AS m ON m.id = e.manager_id
WHERE e.salary > m.salary;
-- (no rows)An empty result is not always a bug. Check the data before "fixing" a query. Here a quick look at the pairs shows the smallest gap is 25000, for Anita and Vikram under Rahul. A more useful question for this data is the size of that gap:
SELECT e.name AS employee, m.name AS manager, m.salary - e.salary AS gap
FROM employees AS e
JOIN employees AS m ON m.id = e.manager_id
ORDER BY gap;
-- Anita Desai | Rahul Sharma | 25000
-- Vikram Singh | Rahul Sharma | 25000
-- Neha Joshi | Farhan Ali | 37000
-- ...Pairs within a group
Another self join finds pairs, for example two employees in the same department who could cover for each other. Join on the same dept_id, and add a.id < b.id. Without it, you would get each person paired with themselves (Meera–Meera) and every pair twice (Meera–Rahul and Rahul–Meera).
SELECT a.name, b.name, a.dept_id
FROM employees AS a
JOIN employees AS b ON a.dept_id = b.dept_id AND a.id < b.id;
-- Meera Iyer | Rahul Sharma | 1
-- ...
-- Farhan Ali | Neha Joshi | 3
-- 10 pairsCROSS JOIN: every combination
A cross join has no ON. It pairs every row of the first table with every row of the second, so the result has rows(a) × rows(b) rows. This is also called the Cartesian product. Usually it is an accident (a forgotten ON), but sometimes all combinations are exactly what you want.
SELECT s.size, c.colour
FROM sizes AS s
CROSS JOIN colours AS c;
-- S | White
-- S | Indigo
-- M | White
-- ... 3 sizes × 2 colours = 6 variantsAnother common use is a calendar grid: every date × every store, then LEFT JOIN the sales onto it. Days with no sales then show as 0 instead of being missing, which matters for charts. Module 6 shows how to generate the dates.
Common mistakes
- Using an inner self join for the employee–manager list and losing the top boss. Use LEFT JOIN.
- Getting each pair twice and people paired with themselves. Add a.id < b.id.
- Mixing up the roles: ON m.id = e.manager_id means "m is e's manager". Writing ON e.id = m.manager_id swaps them.
- An accidental cross join from a missing ON, giving a huge result. Check the row count.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Self joins and cross joins · 1/7
Sometimes a table points to itself. Each employee row has a manager id that points to another employee.
Prefer a video by a teacher? Find free videos on “sql self join employee manager cross join cartesian product” on YouTube ↗
In real life: An org chart in an HR system
HR software stores every employee in one table with a manager_id column. The "reports to" field on a profile page and the approval route for leave requests both come from a self join: find my row, then find the row my manager_id points to. Product catalogues use cross joins to create variants, such as every size of a kurta in every colour.
SELECT e.name AS employee, m.name AS approver
FROM employees AS e
JOIN employees AS m ON m.id = e.manager_id
WHERE e.name = 'Neha Joshi';Practice (0/4 done)
List every employee with the name of their manager. Keep Meera Iyer, who has no manager (her manager should be NULL). Return employee and manager.
Find every pair of employees who work in the same department, each pair once and nobody paired with themselves. Return the first name (from the copy with the smaller id), the second name, and the dept_id.
Nobody in COMPANY earns more than their manager. Instead, show how far below their manager each employee is. For every employee who has a manager, return employee, manager and gap (manager salary minus employee salary), smallest gap first, and for equal gaps by employee name A to Z.
A kurta comes in the sizes and colours stored in the tables sizes(size) and colours(colour). Return every size and colour combination: two columns, size and colour.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Join pitfalls and how to avoid themJOINs · same module
- Joins: combining tablesBackend Development · SQL in depth
- Classic interview queries, part 1Interview questions and practice projects
- OVER and PARTITION BYWindow functions
- Recursive CTEsSubqueries, CTEs and set operations
- Correlated subqueries and derived tablesSubqueries, CTEs and set operations
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Why must a self join use two different aliases?
Answer: SQL forbids a table name twice without them, and you need to say which copy each column comes from
e.name and m.name tell SQL which role each column belongs to.
Pairs of employees in the same department: what does adding a.id < b.id do?
Answer: Removes self pairs and the mirror copy of each pair
Only one ordering of each pair passes the test, and a.id < a.id is never true.
A table of 5 stores is cross joined with a table of 30 dates. How many rows?
Answer: 150
Every store with every date: 5 × 30.
The query "employees who earn more than their manager" returns no rows on COMPANY. What should you do first?
Answer: Look at the data: if every manager earns more, an empty result is correct
An empty result can be the right answer. Check before changing the query.
In FROM employees e JOIN employees m ON m.id = e.manager_id, which copy is the manager?
Answer: m
m.id equals e's manager_id, so m is the row e points to: the manager.
Employees has 11 rows; one has dept_id NULL. How many rows does employees INNER JOIN departments ON d.id = e.dept_id return (every other employee has a valid department)?Related
A query joining 1,000 customers and 10,000 orders returns 1,00,00,000 rows. What most likely went wrong?Related
Answer: The ON condition is missing or wrong
Without a correct ON, every customer is paired with every order: 1,000 x 10,000 rows.
After customers LEFT JOIN orders ... GROUP BY customer, which gives 0 for a customer with no orders?Related
Answer: COUNT(o.id)
COUNT(column) skips NULLs; the unmatched row has o.id NULL.
Which join answers "employees who earn more than their manager"?Related
Answer: A self join: employees e JOIN employees m ON m.id = e.manager_id
Both people are in the same table, so you join the table to itself with two aliases.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 1 more topic in “JOINs” before its exam: Join pitfalls and how to avoid them.
- Module 5 exam: JOINs · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Subqueries, CTEs and set operations. 62 topics left in the course before the final exam.