lesson 19 Joins & sets · intermediate
Keep unmatched rows
Keep rows even when the other table has no match.
the idea
A LEFT JOIN keeps every row of the left table. When no right-side row matches, the right-side columns are NULL. COUNT(right.key) then counts matches, giving zero for unmatched rows, and WHERE right.key IS NULL keeps only the unmatched ones.
Dataset: Retail
The tables the challenges use, with their first 6 rows. Under each column name: the kind of value it holds, and which table it links to.
products6 columns · 5,304 rows
| stock_codetext · unique id | descriptiontext | category_idnumber · links to categories | list_pricedecimal | first_sold_atdate & time | is_merchandisenumber |
|---|---|---|---|---|---|
| 10002 | INFLATABLE POLITICAL GLOBE | 17 | 0.85 | 2009-12-01 09:08:00 | 1 |
| 10002R | ROBOT PENCIL SHARPNER | 8 | 5.17 | 2009-12-02 14:43:00 | 1 |
| 10080 | GROOVY CACTUS INFLATABLE | 17 | 0.39 | 2009-12-02 16:02:00 | 1 |
| 10109 | BENDY COLOUR PENCILS | 8 | 0.42 | 2009-12-03 12:31:00 | 1 |
| 10120 | DOGGY RUBBER | 17 | 0.21 | 2009-12-01 16:17:00 | 1 |
| 10123C | HEARTS WRAPPING TAPE | 8 | 0.65 | 2009-12-01 14:28:00 | 1 |
- stock_codetext · unique id
- 10002
- descriptiontext
- INFLATABLE POLITICAL GLOBE
- category_idnumber · links to categories
- 17
- list_pricedecimal
- 0.85
- first_sold_atdate & time
- 2009-12-01 09:08:00
- is_merchandisenumber
- 1
- stock_codetext · unique id
- 10002R
- descriptiontext
- ROBOT PENCIL SHARPNER
- category_idnumber · links to categories
- 8
- list_pricedecimal
- 5.17
- first_sold_atdate & time
- 2009-12-02 14:43:00
- is_merchandisenumber
- 1
- stock_codetext · unique id
- 10080
- descriptiontext
- GROOVY CACTUS INFLATABLE
- category_idnumber · links to categories
- 17
- list_pricedecimal
- 0.39
- first_sold_atdate & time
- 2009-12-02 16:02:00
- is_merchandisenumber
- 1
- stock_codetext · unique id
- 10109
- descriptiontext
- BENDY COLOUR PENCILS
- category_idnumber · links to categories
- 8
- list_pricedecimal
- 0.42
- first_sold_atdate & time
- 2009-12-03 12:31:00
- is_merchandisenumber
- 1
- stock_codetext · unique id
- 10120
- descriptiontext
- DOGGY RUBBER
- category_idnumber · links to categories
- 17
- list_pricedecimal
- 0.21
- first_sold_atdate & time
- 2009-12-01 16:17:00
- is_merchandisenumber
- 1
- stock_codetext · unique id
- 10123C
- descriptiontext
- HEARTS WRAPPING TAPE
- category_idnumber · links to categories
- 8
- list_pricedecimal
- 0.65
- first_sold_atdate & time
- 2009-12-01 14:28:00
- is_merchandisenumber
- 1
first 6 of 5,304 rows · your queries use all of them
order_items7 columns · 125,613 rows
| order_item_idnumber · unique id | order_idnumber · links to orders | line_numbernumber | stock_codetext · links to products | descriptiontext | quantitynumber | unit_pricedecimal |
|---|---|---|---|---|---|---|
| 1 | 1 | 1 | 21329 | DINOSAURS WRITING SET | 28 | 0.98 |
| 2 | 1 | 2 | 21252 | SET OF MEADOW FLOWER STICKERS | 30 | 1.69 |
| 3 | 1 | 3 | 21100 | CHARLIE AND LOLA CHARLOTTE BAG | 30 | 1.15 |
| 4 | 1 | 4 | 21033 | JUMBO BAG CHARLIE AND LOLA TOYS | 30 | 2 |
| 5 | 1 | 5 | 20711 | JUMBO BAG TOYS | 60 | 1.3 |
| 6 | 1 | 6 | 21410 | COUNTRY COTTAGE DOORSTOP GREEN | 32 | 2.5 |
- order_item_idnumber · unique id
- 1
- order_idnumber · links to orders
- 1
- line_numbernumber
- 1
- stock_codetext · links to products
- 21329
- descriptiontext
- DINOSAURS WRITING SET
- quantitynumber
- 28
- unit_pricedecimal
- 0.98
- order_item_idnumber · unique id
- 2
- order_idnumber · links to orders
- 1
- line_numbernumber
- 2
- stock_codetext · links to products
- 21252
- descriptiontext
- SET OF MEADOW FLOWER STICKERS
- quantitynumber
- 30
- unit_pricedecimal
- 1.69
- order_item_idnumber · unique id
- 3
- order_idnumber · links to orders
- 1
- line_numbernumber
- 3
- stock_codetext · links to products
- 21100
- descriptiontext
- CHARLIE AND LOLA CHARLOTTE BAG
- quantitynumber
- 30
- unit_pricedecimal
- 1.15
- order_item_idnumber · unique id
- 4
- order_idnumber · links to orders
- 1
- line_numbernumber
- 4
- stock_codetext · links to products
- 21033
- descriptiontext
- JUMBO BAG CHARLIE AND LOLA TOYS
- quantitynumber
- 30
- unit_pricedecimal
- 2
- order_item_idnumber · unique id
- 5
- order_idnumber · links to orders
- 1
- line_numbernumber
- 5
- stock_codetext · links to products
- 20711
- descriptiontext
- JUMBO BAG TOYS
- quantitynumber
- 60
- unit_pricedecimal
- 1.3
- order_item_idnumber · unique id
- 6
- order_idnumber · links to orders
- 1
- line_numbernumber
- 6
- stock_codetext · links to products
- 21410
- descriptiontext
- COUNTRY COTTAGE DOORSTOP GREEN
- quantitynumber
- 32
- unit_pricedecimal
- 2.5
first 6 of 125,613 rows · your queries use all of them
worked example
Run these steps in order to see the idea before you write your own query. The step marked “the pattern” is what the challenges ask for. Running the example does not count toward progress.
Find products never ordered. Run this. LEFT JOIN keeps every product; lines with no match show NULL for order_item_id.
step-1.sqlSELECT p.stock_code, p.description, i.order_item_id FROM products p LEFT JOIN order_items i ON i.stock_code = p.stock_code WHERE i.order_item_id IS NULL LIMIT 10;
Count products per category safely. Run this. COUNT(p.stock_code) counts matched products; an empty category shows 0, not 1.the pattern
example.sqlSELECT c.category_id, c.category_name, COUNT(p.stock_code) AS products FROM categories c LEFT JOIN products p ON p.category_id = c.category_id GROUP BY c.category_id, c.category_name;
Compare inner join to full category list. Run this. Inner join only counts categories that have at least one product.
step-3.sqlSELECT ( SELECT COUNT(*) FROM categories ) AS categories, ( SELECT COUNT(DISTINCT c.category_id) FROM categories c JOIN products p ON p.category_id = c.category_id ) AS categories_with_products;
common mistake
COUNT(*) after a LEFT JOIN counts the unmatched row itself, so a product with no lines would show 1 instead of 0.