lesson 19 Joins & sets · intermediate
Keep unmatched rows
Keep rows even when the other table has no match.
the idea
LEFT JOIN keeps every row from the left table. When no match exists, right-side columns are NULL. A right-table filter in WHERE may remove those unmatched rows.
tables in this lesson
The challenges use this data. Each column shows its type underneath; → marks a link to another table.
customers4 columns · 8 rows
| idinteger · key | nametext | citytext | joined_attext |
|---|---|---|---|
| 1 | Alice | Chicago | 2024-01-10 |
| 2 | Ben | Austin | 2024-02-12 |
| 3 | Cara | Seattle | 2024-03-05 |
| 4 | Dev | Chicago | 2024-04-20 |
| 5 | Elena | NULL | 2024-05-01 |
showing 5 of 8 rows
orders6 columns · 12 rows
| idinteger · key | customer_idinteger | product_idinteger | quantityinteger | statustext | ordered_attext |
|---|---|---|---|---|---|
| 101 | 1 | 1 | 2 | paid | 2025-01-01 |
| 102 | 2 | 3 | 1 | paid | 2025-01-02 |
| 103 | 1 | 2 | 1 | paid | 2025-01-05 |
| 104 | 3 | 5 | 2 | pending | 2025-01-08 |
| 105 | 4 | 4 | 5 | paid | 2025-02-01 |
showing 5 of 12 rows
example
The idea in one query. Press Run to see what it returns; running examples never affects your progress.
SELECT c.name, COUNT(o.id) AS order_count FROM customers c LEFT JOIN orders o ON o.customer_id = c.id GROUP BY c.id, c.name;
Watch for: Using INNER JOIN loses Hugo, who has no orders.
build it step by step
Each step changes one thing. Run them in order and watch the result change.
Identify the preserved side. Every customer must stay, even Hugo without an order.
step-1.sqlSELECT * FROM customers;
Inspect the joined rows. Customers repeat for each order. Hugo gets a NULL right-side id.
step-2.sqlSELECT c.id,c.name,o.id AS order_id FROM customers c LEFT JOIN orders o ON o.customer_id=c.id;
Count matching right-side ids. COUNT(o.id) ignores the unmatched NULL and gives Hugo zero.
step-3.sqlSELECT c.name,COUNT(o.id) AS order_count FROM customers c LEFT JOIN orders o ON o.customer_id=c.id GROUP BY c.id,c.name;