All 40 lessons
Lesson 20 of 40 · Joins
SQL: Join Multiple Tables
By Skillancy Editorial Team
Every example runs on the ShopKart dataset, already loaded in the free SQL Compiler. Click Try it in the SQL Compiler under a query to open it there, then press Run.
To join three or more tables, add one JOIN ... ON ... for each table, each connecting to a table already in the query. Real questions, like "revenue by category" or "what did each customer buy?", almost always need more than two tables.
Syntax
SELECT ...
FROM table_a a
JOIN table_b b ON b.a_id = a.id
JOIN table_c c ON c.b_id = b.id
JOIN table_d d ON d.id = c.d_id;The ShopKart path from a customer to a category is:
customers → orders → order_items → products → categoriesExamples
1. Three tables: who bought what
SELECT o.order_id,
c.customer_name,
oi.product_id,
oi.quantity
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id
JOIN order_items oi ON oi.order_id = o.order_id
WHERE o.order_id IN (1001, 1003)
ORDER BY o.order_id;Try it in the SQL CompilerResult:
| order_id | customer_name | product_id | quantity |
|---|---|---|---|
| 1001 | Aarav Sharma | 102 | 2 |
| 1001 | Aarav Sharma | 101 | 1 |
| 1003 | Rohan Gupta | 108 | 3 |
| 1003 | Rohan Gupta | 103 | 1 |
(4 rows)
2. Four tables: full purchase history
SELECT c.customer_name,
o.order_date,
p.product_name,
oi.quantity,
o.status
FROM customers c
JOIN orders o ON o.customer_id = c.customer_id
JOIN order_items oi ON oi.order_id = o.order_id
JOIN products p ON p.product_id = oi.product_id
WHERE c.customer_name = 'Aarav Sharma'
ORDER BY o.order_date;Try it in the SQL CompilerResult:
| customer_name | order_date | product_name | quantity | status |
|---|---|---|---|---|
| Aarav Sharma | 2025-01-05 | Redmi Note 13 Pro | 1 | Delivered |
| Aarav Sharma | 2025-01-05 | boAt Airdopes 141 | 2 | Delivered |
| Aarav Sharma | 2025-02-20 | Running Shoes | 1 | Cancelled |
| Aarav Sharma | 2025-06-30 | Running Shoes | 1 | Delivered |
| Aarav Sharma | 2025-06-30 | Steel Water Bottle 1L | 2 | Delivered |
| Aarav Sharma | 2025-11-15 | Herbal Face Wash | 3 | Delivered |
| Aarav Sharma | 2025-11-15 | Wings of Fire | 2 | Delivered |
(7 rows)
3. Five tables: revenue by category from delivered orders
SELECT cat.category_name,
COUNT(DISTINCT o.order_id) AS orders,
ROUND(SUM(oi.quantity * oi.unit_price * (1 - oi.discount_pct / 100)), 2) AS net_revenue
FROM order_items oi
JOIN orders o ON o.order_id = oi.order_id
JOIN products p ON p.product_id = oi.product_id
JOIN categories cat ON cat.category_id = p.category_id
WHERE o.status = 'Delivered'
GROUP BY cat.category_name
ORDER BY net_revenue DESC;Try it in the SQL CompilerResult:
| category_name | orders | net_revenue |
|---|---|---|
| Electronics | 5 | 79383.65 |
| Fashion | 4 | 19621.35 |
| Home & Kitchen | 4 | 9730.05 |
| Beauty | 4 | 7481.50 |
| Sports | 3 | 3036.20 |
| Books | 3 | 2545.00 |
(6 rows)
This is the kind of query behind a category performance dashboard. COUNT(DISTINCT o.order_id) matters: an order with two Electronics items should count once.
4. Revenue by customer city
SELECT c.city,
ROUND(SUM(oi.quantity * oi.unit_price * (1 - oi.discount_pct / 100)), 2) AS net_revenue
FROM customers c
JOIN orders o ON o.customer_id = c.customer_id
JOIN order_items oi ON oi.order_id = o.order_id
WHERE o.status = 'Delivered'
GROUP BY c.city
ORDER BY net_revenue DESC
LIMIT 5;Try it in the SQL CompilerResult:
| city | net_revenue |
|---|---|
| Delhi | 33183.50 |
| Bengaluru | 28591.30 |
| Lucknow | 24098.05 |
| Kochi | 12071.05 |
| Kolkata | 7649.10 |
(5 rows)
The double-counting trap
Joining a one-to-many relationship repeats the "one" side. Suppose each order had a ₹49 delivery fee and you summed it after joining to order_items:
SELECT COUNT(*) AS rows_after_join,
COUNT(DISTINCT o.order_id) AS actual_orders,
COUNT(*) * 49 AS wrong_fee_total,
COUNT(DISTINCT o.order_id) * 49 AS right_fee_total
FROM orders o
JOIN order_items oi ON oi.order_id = o.order_id;Try it in the SQL CompilerResult:
| rows_after_join | actual_orders | wrong_fee_total | right_fee_total |
|---|---|---|---|
| 38 | 24 | 1862 | 1176 |
(1 row)
38 rows but only 24 orders. Any order-level number summed at line level is inflated. Fixes:
- Aggregate each table to the right level first (with a CTE or subquery), then join.
- Use
COUNT(DISTINCT ...)for counts.
Common mistakes
- A table that doesn't connect. Every joined table needs an
ONcondition linking it to the tables before it. - Mixing LEFT and INNER joins carelessly. An inner join after a left join can silently drop the rows the left join kept.
- Not checking row counts. Run
COUNT(*)after each join you add; a sudden jump means a many-to-many match.
Practice question
Show the total units sold per brand for delivered orders, highest first. Show brand as "Unbranded" if it's missing.
Show solution
SELECT COALESCE(p.brand, 'Unbranded') AS brand,
SUM(oi.quantity) AS units_sold
FROM order_items oi
JOIN orders o ON o.order_id = oi.order_id
JOIN products p ON p.product_id = oi.product_id
WHERE o.status = 'Delivered'
GROUP BY COALESCE(p.brand, 'Unbranded')
ORDER BY units_sold DESC, brand
LIMIT 5;Try it in the SQL CompilerResult:
| brand | units_sold |
|---|---|
| Himalaya | 9 |
| boAt | 6 |
| Jaico | 5 |
| Milton | 5 |
| Boldfit | 4 |
(5 rows)
Key takeaways
- Add one
JOIN ... ON ...per table; each must link to the query so far. - Follow the key path between tables (customers → orders → order_items → products → categories).
- One-to-many joins repeat rows, so watch for double counting.
- Use
COUNT(DISTINCT ...)or pre-aggregate before joining.
Frequently asked questions
How do I join 3 tables in SQL?
Add one JOIN ... ON ... line per extra table. Each new table must connect to one already in the query. For example: orders JOIN customers ON ... JOIN order_items ON ... .
Does the order of joins matter?
For inner joins the result is the same in any order. With LEFT JOINs, order matters: the table written first keeps all its rows, and an inner join later in the chain can remove rows the LEFT JOIN kept.
Why are my totals too high after joining?
A one-to-many join repeats the 'one' side for each match. Summing a column from that side counts it several times. Aggregate at the right level first, then join.
Keep learning SQL
One email a week with SQL and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.
Prefer a one-page summary? Download the free SQL cheat sheet (PDF)