Join Pitfalls

Joins are powerful, and that's why their mistakes are dangerous: most of them give no error, just a wrong result that looks believable. Here are the three most common ones.

1. A forgotten ON. SQLite lets you write JOIN without a condition. Then nothing says which rows belong together, so it pairs every row of the left table with every row of the right one:

SELECT COUNT(*) AS row_count
FROM customers AS c
JOIN orders AS o;
row_count
420

14 customers times 30 orders: 420 rows, every possible combination. Such a join is called a cross join. Sometimes it's what you want (every customer with every game, say), but usually it's a forgotten ON. When a result has far more rows than you expected, check the ON of each join. You may also meet an older way of joining, with the tables separated by commas and the condition in WHERE: FROM customers c, orders o WHERE o.customer_id = c.id. Forget the WHERE there, and you get the same 420 rows.

2. Rows that multiply. A join gives a row for each pair. When you join two tables to the games at once, such as the reviews and the order items, each review of a game is paired with each of its items. Dragon Forge has 4 reviews and was in 4 orders:

SELECT g.title, COUNT(r.stars) AS review_count, SUM(i.quantity) AS copies
FROM games AS g
JOIN reviews AS r ON r.game_id = g.id
JOIN order_items AS i ON i.game_id = g.id
WHERE g.id = 1
GROUP BY g.id, g.title;
titlereview_countcopies
Dragon Forge1616

16 reviews and 16 copies, four times too many: each of the 4 reviews met each of the 4 items, and that makes 16 rows. Nothing tells you that something's wrong; the numbers are just too big. A count can be saved with COUNT(DISTINCT …) of a key: each review is one customer's (the key of reviews is the game and the customer), so COUNT(DISTINCT r.customer_id) counts each review once, however many times its row was repeated. A sum can't be saved this way, because two different items can have the same quantity. Get it with a separate query, or with a subquery, which you'll learn in the next lesson.

3. A condition in ON or in WHERE. With JOIN, it doesn't matter whether a condition is in ON or in WHERE. With LEFT JOIN, it does. How many five-star reviews does each game have?

SELECT g.title, COUNT(r.stars) AS five_stars
FROM games AS g
LEFT JOIN reviews AS r ON r.game_id = g.id
WHERE r.stars = 5
GROUP BY g.id, g.title
ORDER BY g.title;
titlefive_stars
Cube Logic1
Dragon Forge2
Knight's Oath1
……

The games without a five-star review are missing, although that's what LEFT JOIN was there for. WHERE runs after the join: the rows with NULLs and the rows of the other reviews don't pass r.stars = 5, and WHERE throws them out, games and all. The condition belongs in ON:

SELECT g.title, COUNT(r.stars) AS five_stars
FROM games AS g
LEFT JOIN reviews AS r ON r.game_id = g.id AND r.stars = 5
GROUP BY g.id, g.title
ORDER BY g.title;
titlefive_stars
Bakery Story0
Castle Siege0
Cube Logic1
……

A condition in ON decides which rows of the right table count as a match, and LEFT JOIN keeps every row of the left table anyway: a game without a five-star review stays, with NULLs, and COUNT gives it 0. So with LEFT JOIN, a condition on the right table goes in ON, and a condition on the left table in WHERE.

Instructions

Each of the three queries in the editor has one of the pitfalls, and none of them ends with an error. The first one should count the reviews of each game, but it says that every game has 25. Add the missing condition, so that it counts only the reviews of each game. With JOIN, the games without a review aren't in the result, and that's fine here:

titlereview_count
Bakery Story1
Cube Logic2
Dragon Forge4
……

The second one should show the number of reviews and the number of orders of each game, but some numbers are too big: Dragon Forge has 4 reviews and 4 orders, not 16. Fix the counting with DISTINCT, so that each review and each order is counted once:

titlereview_countorder_count
Bakery Story13
Castle Siege01
Cube Logic23
Dragon Forge44
………

The third one should show how many orders each customer placed in 2024, with 0 for the customers who placed none, but those customers are missing. Move the condition to where it belongs:

nameorders_2024
Ava Green1
Emma Clark3
Grace Hall0
Henry Scott0
……

Start programming for free

By signing up, you agree to the Terms of Service and Privacy Policy.

Or sign up with:

11/12