All 40 lessons
Lesson 17 of 40 · Joins
SQL LEFT JOIN and RIGHT 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.
LEFT JOIN returns every row from the left table, plus matching rows from the right table. When there's no match, the right table's columns are NULL. It's the join you need for questions like "all customers and their order count, including zero" or "products that have never sold".
RIGHT JOIN is the mirror image: every row from the right table.
Syntax
SELECT ...
FROM left_table l
LEFT JOIN right_table r
ON l.key = r.key;Examples
1. All customers, with or without orders
SELECT c.customer_name,
o.order_id,
o.status
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE c.customer_id IN (11, 12);Try it in the SQL CompilerResult:
| customer_name | order_id | status |
|---|---|---|
| Farhan Khan | 1018 | Delivered |
| Divya Menon | NULL | NULL |
(2 rows)
Divya Menon (customer 12) has no orders, so her order_id and status are NULL. An inner join would have dropped her.
2. Order count per customer, including zero
SELECT c.customer_name,
COUNT(o.order_id) AS orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
GROUP BY c.customer_name
ORDER BY orders, c.customer_name
LIMIT 5;Try it in the SQL CompilerResult:
| customer_name | orders |
|---|---|
| Divya Menon | 0 |
| Farhan Khan | 1 |
| Ishita Das | 1 |
| Ananya Iyer | 2 |
| Arjun Patel | 2 |
(5 rows)
Use COUNT(o.order_id), not COUNT(*). COUNT(*) would count Divya's NULL row as 1.
3. Find rows with no match (anti-join)
Products that have never been sold: LEFT JOIN, then keep rows where the right side is NULL:
SELECT p.product_id, p.product_name
FROM products p
LEFT JOIN order_items oi ON oi.product_id = p.product_id
WHERE oi.order_item_id IS NULL;Try it in the SQL CompilerResult:
| product_id | product_name |
|---|---|
| 115 | Cricket Bat (Kashmir Willow) |
(1 row)
Customers who have never ordered:
SELECT c.customer_name, c.signup_date
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE o.order_id IS NULL;Try it in the SQL CompilerResult:
| customer_name | signup_date |
|---|---|
| Divya Menon | 2025-10-02 |
(1 row)
These "never bought" lists are the starting point for re-engagement campaigns.
4. RIGHT JOIN
The same as example 3, written with the tables swapped:
SELECT p.product_id, p.product_name
FROM order_items oi
RIGHT JOIN products p ON oi.product_id = p.product_id
WHERE oi.order_item_id IS NULL;Try it in the SQL CompilerResult:
| product_id | product_name |
|---|---|
| 115 | Cricket Bat (Kashmir Willow) |
(1 row)
Same result. Most analysts stick to LEFT JOIN and put the "keep all" table first.
The WHERE vs ON trap
Say you want every customer with their count of delivered orders. Putting the status filter in WHERE quietly turns the LEFT JOIN into an INNER JOIN, because NULL statuses fail the condition:
-- Wrong: customers with no delivered orders disappear
SELECT c.customer_name, COUNT(o.order_id) AS delivered_orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
WHERE o.status = 'Delivered'
GROUP BY c.customer_name
ORDER BY delivered_orders, c.customer_name
LIMIT 4;Try it in the SQL CompilerResult:
| customer_name | delivered_orders |
|---|---|
| Ananya Iyer | 1 |
| Farhan Khan | 1 |
| Ishita Das | 1 |
| Karan Mehta | 1 |
(4 rows)
Move the condition into ON, so it filters the right table before the join:
-- Right: every customer stays, with 0 where appropriate
SELECT c.customer_name, COUNT(o.order_id) AS delivered_orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.customer_id
AND o.status = 'Delivered'
GROUP BY c.customer_name
ORDER BY delivered_orders, c.customer_name
LIMIT 4;Try it in the SQL CompilerResult:
| customer_name | delivered_orders |
|---|---|
| Arjun Patel | 0 |
| Divya Menon | 0 |
| Ananya Iyer | 1 |
| Farhan Khan | 1 |
(4 rows)
Arjun (whose only orders are cancelled or pending) and Divya (no orders at all) now appear with 0. The rule: conditions on the right table of a LEFT JOIN go in ON.
Common mistakes
- Filtering the right table in WHERE, as shown above.
- Using
COUNT(*)after a LEFT JOIN, which counts unmatched rows as 1. - Chaining an INNER JOIN after a LEFT JOIN.
customers LEFT JOIN orders JOIN order_itemsdrops customers without orders again. Keep later joins as LEFT JOINs too.
Practice question
List every category with the number of products it contains, including categories with no products. (All six categories currently have products, so every count will be above zero.)
Show solution
SELECT cat.category_name,
COUNT(p.product_id) AS products
FROM categories cat
LEFT JOIN products p ON p.category_id = cat.category_id
GROUP BY cat.category_name
ORDER BY products DESC, cat.category_name;Try it in the SQL CompilerResult:
| category_name | products |
|---|---|
| Electronics | 3 |
| Fashion | 3 |
| Home & Kitchen | 3 |
| Beauty | 2 |
| Books | 2 |
| Sports | 2 |
(6 rows)
Key takeaways
LEFT JOINkeeps every row from the left table; unmatched right columns are NULL.LEFT JOIN ... WHERE right.key IS NULLfinds rows with no match.- Put conditions on the right-hand table in
ON, notWHERE. RIGHT JOINis aLEFT JOINwith the tables swapped.
Frequently asked questions
What is the difference between LEFT JOIN and INNER JOIN?
INNER JOIN returns only rows that match in both tables. LEFT JOIN returns every row from the left table, filling the right table's columns with NULL when there is no match.
Is LEFT JOIN the same as LEFT OUTER JOIN?
Yes. OUTER is optional.
When should I use RIGHT JOIN?
Rarely. Any RIGHT JOIN can be rewritten as a LEFT JOIN by swapping the table order, and most teams prefer LEFT JOIN for readability.
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)