SQL joins explained, with one example of each
A join combines rows from two tables by matching a column. The join type decides what happens to rows that find no partner. All examples use the bakery database: 8 customers, and 12 orders, two of which were walk-in orders with no customer.
INNER JOIN: only the matches
Keeps a row only when both sides have a match. Customers without orders and orders without a customer both disappear.
SELECT c.last_name, o.order_id FROM customers AS c INNER JOIN orders AS o ON o.customer_id = c.customer_id ORDER BY o.order_id;
Result: 10 rows: the orders that belong to a customer.
LEFT JOIN: keep everything on the left
Keeps every row of the left table. Where the right table has no match, its columns are NULL. Filtering on IS NULL finds the rows without a match.
SELECT c.last_name, COUNT(o.order_id) AS orders FROM customers AS c LEFT JOIN orders AS o ON o.customer_id = c.customer_id GROUP BY c.customer_id, c.last_name ORDER BY c.customer_id;
Result: All 8 customers; Kim and Meyer show 0 orders.
RIGHT JOIN: keep everything on the right
The mirror image of LEFT JOIN. Most people swap the table order and use LEFT JOIN instead, which reads more naturally.
SELECT o.order_id, c.last_name FROM customers AS c RIGHT JOIN orders AS o ON o.customer_id = c.customer_id ORDER BY o.order_id;
Result: All 12 orders; orders 4 and 11 have a NULL last name (walk-ins).
FULL JOIN: keep everything on both sides
Keeps unmatched rows from both tables. It is useful to compare two lists and see what is missing on either side.
SELECT c.last_name, o.order_id FROM customers AS c FULL JOIN orders AS o ON o.customer_id = c.customer_id ORDER BY o.order_id, c.last_name;
Result: 14 rows: 10 matches, 2 walk-in orders, and the customers Kim and Meyer with no order.
CROSS JOIN: every combination
Pairs each row of the first table with each row of the second. The row count is the product of the two. Use it on purpose, for example to build a grid of dates and categories.
SELECT COUNT(*) AS combinations FROM customers CROSS JOIN products;
Result: 72, which is 8 customers times 9 products.
Self join: a table with itself
Give the same table two aliases to compare rows of the same table, such as two customers in the same city.
SELECT a.first_name AS first_customer, b.first_name AS second_customer, a.city FROM customers AS a JOIN customers AS b ON a.city = b.city AND a.customer_id < b.customer_id ORDER BY a.city;
Result: The pairs Emma and Hugo (Berlin), Ben and Dev (London), and Chloe and Farid (Paris).
Common questions
- Why does my join return more rows than I expected?
- Usually the join column is not unique on one side, so each row matches several rows. Check the keys, and test the row count before and after the join.
- Does it matter whether a condition is in ON or in WHERE?
- For an INNER JOIN it makes no difference. For a LEFT JOIN a condition on the right table in WHERE removes the unmatched rows and turns the LEFT JOIN into an INNER JOIN. Put it in ON to keep them.
- Which join is the same as VLOOKUP?
- A LEFT JOIN: every row of the main table is kept, and the looked-up column is NULL where nothing matches, like #N/A. Unlike VLOOKUP it returns all matches, not just the first.