Joining two tables
The orders table records which customer placed each order, but only by the customer's number:
1SELECT * FROM orders ORDER BY id;| id | customer_id | order_date | status |
|---|---|---|---|
| 1 | 1 | 2025-09-02 | shipped |
| 2 | 2 | 2025-09-05 | shipped |
| 3 | 1 | 2025-09-18 | shipped |
| 4 | 4 | 2025-09-21 | cancelled |
| 5 | 5 | 2025-10-01 | shipped |
| 6 | 3 | 2025-10-03 | shipped |
| 7 | 2 | 2025-10-10 | pending |
| 8 | 7 | 2025-10-12 | shipped |
| 9 | 1 | 2025-10-15 | pending |
| 10 | 8 | 2025-10-20 | shipped |
| 11 | 5 | 2025-10-22 | shipped |
| 12 | 4 | 2025-10-25 | pending |
Order 4 was placed by the customer whose customer_id is 4. What is that customer's name, and which city do they live in? For that you have to look in the customers table. This lesson is about using two tables in one query.
Why several tables?
Couldn't each order just hold the customer's name and city? It could, but customer 1 has placed 3 orders, so her name and city would be written 3 times. If she moves one day, all 3 places have to change; miss one and the data contradicts itself.
So databases usually store it like this: a customer's details are written once, in the customers table, and an order holds only the customer's number. When you need both, you join the two tables by that number.
JOIN … ON
FROM table1 JOIN table2 ON condition pairs up the rows of the two tables, and ON says how to pair them. Here, the order's customer_id equals the customer's id:
1SELECT orders.id, orders.order_date, customers.name2FROM orders3JOIN customers ON orders.customer_id = customers.id4ORDER BY orders.id;
| id | order_date | name |
|---|---|---|
| 1 | 2025-09-02 | Ana Silva |
| 2 | 2025-09-05 | Ben Okafor |
| 3 | 2025-09-18 | Ana Silva |
| 4 | 2025-09-21 | David Kim |
| 5 | 2025-10-01 | Emma Novak |
| 6 | 2025-10-03 | Chloe Martin |
| 7 | 2025-10-10 | Ben Okafor |
| 8 | 2025-10-12 | George Brown |
| 9 | 2025-10-15 | Ana Silva |
| 10 | 2025-10-20 | Hana Sato |
| 11 | 2025-10-22 | Emma Novak |
| 12 | 2025-10-25 | David Kim |
A column name both tables have (such as id) is written table.column, to say which table it comes from.
Press Step and look at the JOIN step: each order takes its customer_id to the customers table, finds the row with the same id, and the two are put together into one row. 12 orders make 12 rows.
JOIN can also be written INNER JOIN; it means exactly the same.
Table aliases
Writing the table names out every time gets long. Give each table a short name in FROM and JOIN, and use the short name from then on:
1SELECT o.id, o.order_date, c.name, c.city2FROM orders AS o3JOIN customers AS c ON o.customer_id = c.id4ORDER BY o.id;
| id | order_date | name | city |
|---|---|---|---|
| 1 | 2025-09-02 | Ana Silva | London |
| 2 | 2025-09-05 | Ben Okafor | Paris |
| 3 | 2025-09-18 | Ana Silva | London |
| 4 | 2025-09-21 | David Kim | Tokyo |
| 5 | 2025-10-01 | Emma Novak | Berlin |
| 6 | 2025-10-03 | Chloe Martin | London |
| 7 | 2025-10-10 | Ben Okafor | Paris |
| 8 | 2025-10-12 | George Brown | Tokyo |
| 9 | 2025-10-15 | Ana Silva | London |
| 10 | 2025-10-20 | Hana Sato | London |
| 11 | 2025-10-22 | Emma Novak | Berlin |
| 12 | 2025-10-25 | David Kim | Tokyo |
orders AS o means "the orders table, called o from here on". The AS can be left out: orders o means the same.
Columns with the same name
A column that only one table has, such as order_date or name, can go without the table name. A column both tables have can't:
1SELECT id, name, order_date2FROM orders AS o3JOIN customers AS c ON o.customer_id = c.id;
ERROR 1052 (23000): Column 'id' in field list is ambiguous
ERROR 1052: column id is "ambiguous". The orders table has an id, and so does the customers table, and the database doesn't know which one you mean. Write o.id or c.id.
Tip: In a join, put the table alias on every column (o.id, c.name), even where leaving it out would work. Anyone reading can see at a glance where each column comes from.
Unmatched rows are gone
Now the other way round, from the customers to their orders:
1SELECT c.id, c.name, o.id AS order_id2FROM customers AS c3JOIN orders AS o ON o.customer_id = c.id4ORDER BY c.id, o.id;
| id | name | order_id |
|---|---|---|
| 1 | Ana Silva | 1 |
| 1 | Ana Silva | 3 |
| 1 | Ana Silva | 9 |
| 2 | Ben Okafor | 2 |
| 2 | Ben Okafor | 7 |
| 3 | Chloe Martin | 6 |
| 4 | David Kim | 4 |
| 4 | David Kim | 12 |
| 5 | Emma Novak | 5 |
| 5 | Emma Novak | 11 |
| 7 | George Brown | 8 |
| 8 | Hana Sato | 10 |
Press Step again and watch the pairing: customer 1 matches 3 orders and becomes 3 rows; customers 6, 9 and 10 have no orders at all. No match, no row: they aren't in the result at all.
JOIN keeps only rows that found a partner on both sides. To keep the customers who never ordered as well, you need lesson 12's LEFT JOIN.
The products table has 10 rows, order_items has 18, and nobody has ever bought the desk lamp (product 6). How many rows does the result have?
1SELECT p.name, oi.order_id2FROM products AS p3JOIN order_items AS oi ON oi.product_id = p.id4ORDER BY oi.order_id, p.id;
Every order line finds its product, so all 18 lines are matched and make 18 rows. The desk lamp matches no line, so it isn't in the result; a product that appears in several orders shows up several times.
JOIN with WHERE
A joined result is like any table: WHERE and ORDER BY work on it as usual. Which orders did customers in London place?
1SELECT o.id, o.order_date, c.name2FROM orders AS o3JOIN customers AS c ON o.customer_id = c.id4WHERE c.city = 'London'5ORDER BY o.id;
| id | order_date | name |
|---|---|---|
| 1 | 2025-09-02 | Ana Silva |
| 3 | 2025-09-18 | Ana Silva |
| 6 | 2025-10-03 | Chloe Martin |
| 9 | 2025-10-15 | Ana Silva |
| 10 | 2025-10-20 | Hana Sato |
city is in the customers table and order_date in the orders table; after the join, each row has both, so WHERE can use a column from either table.
What's in an order
order_items has only product numbers. Join the products table and you can see what order 3 contains:
1SELECT p.name, oi.quantity, oi.unit_price2FROM order_items AS oi3JOIN products AS p ON oi.product_id = p.id4WHERE oi.order_id = 35ORDER BY p.id;
| name | quantity | unit_price |
|---|---|---|
| Headphones | 1 | 59.00 |
| USB cable | 2 | 8.50 |
Joining three tables at once (orders → order lines → products) comes in lesson 13.
Practice
Write the query, then press Check. A hint costs nothing; the answer is there when you need it.
Pending orders
✓Get all pending orders: the order's id and order_date, and the name of the customer who placed it, sorted by order id.
| id | order_date | name |
|---|---|---|
| 7 | 2025-10-10 | Ben Okafor |
| 9 | 2025-10-15 | Ana Silva |
| 12 | 2025-10-25 | David Kim |
1SELECT id, order_date, customer_id2FROM orders3WHERE status = 'pending';
Electronics sold
✓From the order lines, get every line for an electronics product: order_id, the product's name, and quantity, sorted by order_id, and within an order by product number.
| order_id | name | quantity |
|---|---|---|
| 3 | Headphones | 1 |
| 3 | USB cable | 2 |
| 4 | Phone case | 1 |
| 8 | Headphones | 1 |
| 10 | USB cable | 1 |
| 10 | Phone case | 1 |
1SELECT order_id, product_id, quantity FROM order_items;Fix it: a shared column name
✓This should get the orders placed by customers in Tokyo: the order's id, the customer's name, and order_date, sorted by order id. But it gives ERROR 1052. Fix it.
| id | name | order_date |
|---|---|---|
| 4 | David Kim | 2025-09-21 |
| 8 | George Brown | 2025-10-12 |
| 12 | David Kim | 2025-10-25 |
1SELECT id, name, order_date2FROM orders AS o3JOIN customers AS c ON o.customer_id = c.id4WHERE city = 'Tokyo'5ORDER BY id;
What you learned
- Data is split across tables so that each fact is written once; the tables are linked by id numbers.
FROM table1 JOIN table2 ON conditionputs matching rows together into one;JOINis the same asINNER JOIN.- Give tables aliases (
orders AS o) and write columns aso.id; a column both tables have, without a table name, givesERROR 1052. - Rows with no match don't appear in the result; to keep them, use lesson 12's
LEFT JOIN. - A joined result works with
WHEREandORDER BYas usual.