Level 10: LEFT JOIN and multiple joins
Updated on
Intermediate · level 10 of 20. Goal: Keep rows without a match, chain several joins and join a table to itself.
Topics: LEFT JOIN rows without a match several JOINs self-join
Lesson
JOIN makes the rows without a match disappear. Yet those are often the ones that matter: the customers who never ordered, the books never borrowed. LEFT JOIN keeps them.
LEFT JOIN keeps all the rows of the left table, the one written in the FROM. When a row has a match, you get the same thing as with JOIN. When it has none, it is kept anyway, and all the columns of the right table are NULL. FROM directors d LEFT JOIN movies m … thus keeps Sara Diaz, with a NULL title.
To find the “orphans”, you do a LEFT JOIN then WHERE right.id IS NULL: only the left rows without a match remain. To count, use COUNT(right.id), which is 0 for a row without a match; COUNT(*) would count 1, because the row does exist in the result.
Classic trap: a condition on the right table placed in the WHERE. The WHERE runs after the join; for a row without a match, the right column is NULL, the test fails and the row disappears, just as with an ordinary JOIN. To filter the right table without losing the left rows, put the condition in the ON: LEFT JOIN orders o ON o.customer_id = c.id AND o.status = 'shipped'. Challenge 10.5 makes you meet this trap.
You can chain as many joins as needed, each with its own ON. A table can also be joined to itself: this is a self-join. In staff, manager_id points to the id of another employee. FROM staff e LEFT JOIN staff m ON m.id = e.manager_id reads the table twice under two aliases: e for the employee, m for their manager. m.name then gives the manager's name, and the LEFT JOIN keeps Alice, who has none.
Syntax
SELECT …
FROM a
LEFT JOIN b ON b.a_id = a.id
JOIN c ON c.id = b.c_id;Worked example
SELECT d.name, m.title
FROM directors d
LEFT JOIN movies m ON m.director_id = d.id;Every director is listed. Sara Diaz, who has no movie, appears with an empty title (NULL).
| id | name | country |
|---|---|---|
| 1 | Nora Ellis | UK |
| 2 | Paulo Reis | Brazil |
| 3 | Kenji Mori | Japan |
| 4 | Anna Berg | Sweden |
| 5 | Luc Martin | France |
| 6 | Sara Diaz | Spain |
| id | title | genre | year | duration | rating | director_id |
|---|---|---|---|---|---|---|
| 1 | Night Train | Thriller | 2015 | 118 | 7.8 | 1 |
| 2 | Blue Harbor | Drama | 2018 | 102 | 7.1 | 2 |
| 3 | Paper Moon City | Comedy | 2012 | 95 | 6.4 | 5 |
| 4 | Silent Peak | Drama | 2020 | 131 | 8.2 | 3 |
| 5 | Last Signal | Sci-Fi | 2019 | 142 | 7.5 | 1 |
| 6 | Summer Keys | Comedy | 2016 | 88 | 5.9 | 4 |
| 7 | Iron Garden | Sci-Fi | 2021 | 125 | 8 | 3 |
| 8 | Dust and Gold | Western | 2014 | 110 | 6.8 | 2 |
| 9 | The Quiet Hour | Drama | 2022 | 97 | 7.4 | 5 |
| 10 | Deep Current | Thriller | 2017 | 105 | 6.9 | NULL |
Example result
| name | title |
|---|---|
| Nora Ellis | Last Signal |
| Nora Ellis | Night Train |
| Paulo Reis | Blue Harbor |
| Paulo Reis | Dust and Gold |
| Kenji Mori | Iron Garden |
| Kenji Mori | Silent Peak |
| Anna Berg | Summer Keys |
| Luc Martin | Paper Moon City |
| Luc Martin | The Quiet Hour |
| Sara Diaz | NULL |
Key points
- LEFT JOIN keeps the whole left table.
- LEFT JOIN + IS NULL finds the “orphans”.
- A self-join uses two aliases for the same table.
Common pitfalls
- A WHERE on a column of the right table can cancel the effect of the LEFT JOIN.
- COUNT(*) counts 1 even when there is no match: use COUNT(right_column).
Going further
RIGHT JOIN keeps the whole right table and FULL JOIN keeps both sides (available in SQLite since version 3.39). CROSS JOIN pairs every row with every row of the other table: this is the Cartesian product, useful to generate all combinations.
The level’s 5 exercises
- Show the name of each artist and their number of songs, including the artists who have none (0).
- Show the name of the members who never borrowed a book.
- For each loan, show the member's name, the book title and the loan date.
- Show the name of each employee and the name of their manager (empty for the person who has none).
- For each customer, show their name and the number of their orders not shipped, that is whose status is different from 'shipped' (not_shipped). Customers with none must appear with 0.
In SpeedQL, every query is checked straight away: its result is compared with the expected one, then the query is run again on a hidden control database. Each exercise has written hints, to show only if you get stuck.
See also: the cheat sheet card · the SQL exercises with solutions on this topic
Do the level 10 exercises
Free, no sign-up: the lesson and the 5 exercises open right in your browser.
← Previous level: Linking two tables with JOIN · Next level: Conditions in the result: CASE →