All 40 lessons
Lesson 16 of 40 · Joins
SQL Joins and INNER JOIN
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.
A join combines rows from two tables by matching values in a related column. ShopKart keeps orders in one table and customers in another. To show who placed each order, you join orders to customers on customer_id.
INNER JOIN keeps only rows that have a match in both tables.
Why data is split across tables
Storing the customer's name on every order would repeat it dozens of times, and changing it would mean updating every order. Instead, each order stores only customer_id, and the name lives once in customers. Joins put the pieces back together when you query. (This design is called normalization.)
Syntax
SELECT a.column, b.column
FROM table_a AS a
INNER JOIN table_b AS b
ON a.key = b.key;Types of joins at a glance
| Join | Returns |
|---|---|
INNER JOIN | only rows that match in both tables |
LEFT JOIN | all rows from the left table, matches from the right (or NULL) |
RIGHT JOIN | all rows from the right table, matches from the left (or NULL) |
FULL OUTER JOIN | all rows from both tables |
CROSS JOIN | every row paired with every row |
This lesson covers INNER JOIN. The others follow in the next lessons.
Examples
1. Orders with customer names
SELECT o.order_id,
o.order_date,
c.customer_name,
c.city
FROM orders AS o
INNER JOIN customers AS c
ON o.customer_id = c.customer_id
ORDER BY o.order_id
LIMIT 5;Try it in the SQL CompilerResult:
| order_id | order_date | customer_name | city |
|---|---|---|---|
| 1001 | 2025-01-05 | Aarav Sharma | Bengaluru |
| 1002 | 2025-01-18 | Priya Nair | Kochi |
| 1003 | 2025-02-02 | Rohan Gupta | Delhi |
| 1004 | 2025-02-20 | Aarav Sharma | Bengaluru |
| 1005 | 2025-03-11 | Sneha Reddy | Hyderabad |
(5 rows)
For each order, SQL finds the customer row with the same customer_id and glues the two rows together.
2. Order lines with product names
SELECT oi.order_id,
p.product_name,
oi.quantity,
oi.unit_price
FROM order_items oi
JOIN products p ON oi.product_id = p.product_id
ORDER BY oi.order_id
LIMIT 5;Try it in the SQL CompilerResult:
| order_id | product_name | quantity | unit_price |
|---|---|---|---|
| 1001 | boAt Airdopes 141 | 2 | 1299.00 |
| 1001 | Redmi Note 13 Pro | 1 | 23999.00 |
| 1002 | Women's Silk Saree | 1 | 8499.00 |
| 1003 | Steel Water Bottle 1L | 3 | 549.00 |
| 1003 | Samsung 43" Smart TV | 1 | 31990.00 |
(5 rows)
JOIN on its own means INNER JOIN, and AS is optional for table aliases.
3. Join, then aggregate
Revenue by product name, the query we couldn't write in the GROUP BY lesson:
SELECT p.product_name,
SUM(oi.quantity) AS units,
ROUND(SUM(oi.quantity * oi.unit_price * (1 - oi.discount_pct / 100)), 2) AS net_revenue
FROM order_items oi
JOIN products p ON p.product_id = oi.product_id
GROUP BY p.product_name
ORDER BY net_revenue DESC
LIMIT 5;Try it in the SQL CompilerResult:
| product_name | units | net_revenue |
|---|---|---|
| Redmi Note 13 Pro | 3 | 68397.15 |
| Samsung 43" Smart TV | 2 | 55982.50 |
| Women's Silk Saree | 3 | 24222.15 |
| boAt Airdopes 141 | 6 | 7794.00 |
| Men's Cotton Kurta | 4 | 7501.05 |
(5 rows)
4. Join with a filter
Delivered orders from Prime members:
SELECT o.order_id, c.customer_name, o.order_date
FROM orders o
JOIN customers c ON c.customer_id = o.customer_id
WHERE c.is_prime
AND o.status = 'Delivered'
ORDER BY o.order_date
LIMIT 6;Try it in the SQL CompilerResult:
| order_id | customer_name | order_date |
|---|---|---|
| 1001 | Aarav Sharma | 2025-01-05 |
| 1003 | Rohan Gupta | 2025-02-02 |
| 1005 | Sneha Reddy | 2025-03-11 |
| 1008 | Karan Mehta | 2025-05-03 |
| 1009 | Rohan Gupta | 2025-05-21 |
| 1011 | Aarav Sharma | 2025-06-30 |
(6 rows)
5. USING as a shortcut
When the join column has the same name in both tables:
SELECT order_id, customer_name
FROM orders
JOIN customers USING (customer_id)
LIMIT 3;Try it in the SQL CompilerResult:
| order_id | customer_name |
|---|---|
| 1001 | Aarav Sharma |
| 1002 | Priya Nair |
| 1003 | Rohan Gupta |
(3 rows)
INNER JOIN drops unmatched rows
ShopKart has 12 customers, but Divya Menon has never ordered. An inner join from customers to orders leaves her out:
SELECT COUNT(DISTINCT c.customer_id) AS customers_in_result
FROM customers c
JOIN orders o ON o.customer_id = c.customer_id;Try it in the SQL CompilerResult:
| customers_in_result |
|---|
| 11 |
(1 row)
That's correct for "customers who ordered" but wrong for "all customers and their order count". For that you need a LEFT JOIN.
Common mistakes
Ambiguous column names. When both tables have customer_id, say which one:
SELECT customer_id, customer_name
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id;Try it in the SQL CompilerError: column reference "customer_id" is ambiguous
Fix: SELECT o.customer_id, ....
- Joining on the wrong column.
ON o.order_id = c.customer_idruns without error and returns nonsense. Always join a foreign key to the key it references. - Forgetting that joins multiply rows. One order with three items becomes three rows after joining to
order_items. Summing an order-level value afterwards double counts.
Practice question
List each order line with the customer's name and the product's name for order 1001. (Hint: you need three tables. Joining more than two is covered in lesson 20, but try it.)
Show solution
SELECT o.order_id, c.customer_name, p.product_name, 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
JOIN products p ON p.product_id = oi.product_id
WHERE o.order_id = 1001;Try it in the SQL CompilerResult:
| order_id | customer_name | product_name | quantity |
|---|---|---|---|
| 1001 | Aarav Sharma | Redmi Note 13 Pro | 1 |
| 1001 | Aarav Sharma | boAt Airdopes 141 | 2 |
(2 rows)
Key takeaways
- Joins combine tables by matching related columns.
INNER JOIN(or justJOIN) returns only matching rows.- Use table aliases and prefix columns that exist in both tables.
- Joins can multiply rows; check counts before you sum.
Frequently asked questions
What are the types of joins in SQL?
INNER JOIN (only matching rows), LEFT JOIN and RIGHT JOIN (all rows from one side plus matches), FULL OUTER JOIN (all rows from both sides) and CROSS JOIN (every combination). A self join is any of these with a table joined to itself.
Is JOIN the same as INNER JOIN?
Yes. JOIN on its own means INNER JOIN.
What is the difference between JOIN ON and JOIN USING?
ON lets you write any matching condition, such as o.customer_id = c.customer_id. USING (customer_id) is a shortcut when the column has the same name in both tables, and it shows that column only once in SELECT *.
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)