ShareYourGroupShareYourGroupSQL

Lesson 15 of 26 · see the practice database

LEFT JOIN and missing matches

An inner join drops rows that have no partner. Often those are exactly the rows you care about: the customers who have not ordered, the products that have not sold. A LEFT JOIN keeps them.

The idea

A LEFT JOIN B returns every row of A. Where a row of A has a match in B, the columns of B are filled in. Where it has none, the columns of B are NULL.

"Left" simply means the table written first, on the left of the word JOIN.

SELECT c.name, o.id AS order_id, o.order_date
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
ORDER BY c.name;

Result

nameorder_idorder_date
Arjun MehtaNULLNULL
Asha Verma12026-01-05
Asha Verma42026-02-02
Asha Verma122026-05-06
Asha Verma202026-10-04
Imran Ali82026-03-15
Imran Ali152026-06-21
Joy Thomas142026-06-02
Kabir Khan32026-01-20
Kabir Khan102026-04-01
Kabir Khan182026-09-01
Meena GillNULLNULL

23 rows

Showing the first 12 of 23 rows.

Every customer is here. A customer with three orders appears three times. A customer with no orders appears once, with NULL in the order columns.

Compare the row counts of the two kinds of join:

SELECT
  (SELECT COUNT(*) FROM customers c JOIN orders o ON o.customer_id = c.id) AS inner_rows,
  (SELECT COUNT(*) FROM customers c LEFT JOIN orders o ON o.customer_id = c.id) AS left_rows;

Result

inner_rowsleft_rows
2023

1 row

The difference is the number of customers without orders.

Finding rows with no match

This is the most valuable use of a left join. Keep everything from the left, then keep only the rows where the right side came back empty.

SELECT c.name, c.city
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;

Result

namecity
Meena GillAmritsar
Arjun MehtaPune
Tara SinghLudhiana

3 rows

These are the customers who have never ordered. The test must be IS NULL on a column of the right-hand table that can never be NULL in a real match. The primary key, o.id, is the safe choice.

Which product has nobody ever bought?

SELECT p.name, p.category
FROM products p
LEFT JOIN order_items oi ON oi.product_id = p.id
WHERE oi.product_id IS NULL;

Result

namecategory
Wall ClockHome

1 row

Counting, including the zeros

In lesson 12, a count of orders per customer left out the customers with none. A left join puts them back. There is one thing to get right: count a column from the right table, not *.

SELECT c.name, COUNT(o.id) AS orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY orders DESC, c.name;

Result

nameorders
Asha Verma4
Kabir Khan3
Ravi Kumar3
Imran Ali2
Neha Joshi2
Priya Nair2
Sunita Rao2
Joy Thomas1
Vikram Das1
Arjun Mehta0
Meena Gill0
Tara Singh0

12 rows

COUNT(o.id) skips the NULLs, so a customer with no orders gets 0. COUNT(*) would count the single row that the left join keeps for that customer and report 1, which is wrong. Try it and see.

Totals work the same way, with COALESCE to turn a missing total into zero.

SELECT p.name, COALESCE(SUM(oi.quantity), 0) AS units_sold
FROM products p
LEFT JOIN order_items oi ON oi.product_id = p.id
GROUP BY p.id, p.name
ORDER BY units_sold
LIMIT 5;

Result

nameunits_sold
Wall Clock0
Earphones2
Power Bank2
Pressure Cooker 3 L2
Tea 500 g3

5 rows

The trap: a WHERE that undoes the left join

Suppose you want every customer, with their delivered orders if they have any. This looks right and is not.

SELECT c.name, o.id AS order_id, o.status
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'delivered'
ORDER BY c.name;

Result

nameorder_idstatus
Asha Verma1delivered
Asha Verma4delivered
Asha Verma12delivered
Imran Ali8delivered
Imran Ali15delivered
Joy Thomas14delivered
Kabir Khan10delivered
Priya Nair6delivered
Ravi Kumar2delivered
Ravi Kumar7delivered
Ravi Kumar16delivered
Sunita Rao5delivered

13 rows

Showing the first 12 of 13 rows.

The customers with no orders have vanished. For them o.status is NULL, the test NULL = 'delivered' is not true, and WHERE throws the row out. The left join has quietly become an inner join.

The cure is to put the condition on the right-hand table into the ON, so that it is part of the matching and not a filter afterwards.

SELECT c.name, o.id AS order_id, o.status
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id AND o.status = 'delivered'
ORDER BY c.name;

Result

nameorder_idstatus
Arjun MehtaNULLNULL
Asha Verma1delivered
Asha Verma4delivered
Asha Verma12delivered
Imran Ali8delivered
Imran Ali15delivered
Joy Thomas14delivered
Kabir Khan10delivered
Meena GillNULLNULL
Neha JoshiNULLNULL
Priya Nair6delivered
Ravi Kumar2delivered

17 rows

Showing the first 12 of 17 rows.

Now every customer appears. The rule to remember:

For an inner join the two give the same result. For a left join they do not.

RIGHT JOIN

A RIGHT JOIN B keeps every row of B. It is a left join written the other way round, and you can always get the same result by swapping the tables and using LEFT JOIN. Most people use only left joins, so that "the table I am keeping everything from" is always the first one. Here is the "never ordered" query from above, written with a right join. It returns the same customers:

SELECT c.name, c.city
FROM orders o
RIGHT JOIN customers c ON o.customer_id = c.id
WHERE o.id IS NULL;

Result

namecity
Meena GillAmritsar
Arjun MehtaPune
Tara SinghLudhiana

3 rows

FULL JOIN

A FULL JOIN, also written FULL OUTER JOIN, keeps the unmatched rows of both tables. It is useful for comparing two lists, such as this month's customers against last month's. PostgreSQL, SQL Server, Oracle and recent versions of SQLite have it. MySQL does not; there, people combine a left join and a right join with UNION.

The join family

JoinKeeps
INNER JOINonly rows with a match on both sides
LEFT JOINall rows of the first table
RIGHT JOINall rows of the second table
FULL JOINall rows of both tables
CROSS JOINevery row paired with every row

The word OUTER may be written in the middle three, as in LEFT OUTER JOIN. It changes nothing.

Which join should I use?

Ask what should happen to a row that has no partner. If it should disappear, use an inner join. If it should stay, with blanks, use a left join starting from the table whose rows must all be kept. When in doubt, compare the row counts of both and look at the rows that differ.

Try it yourself

Exercise 1. Show every product's name with the number of order lines it appears in, including products with zero. Name the count lines.

Solve it in the practice box

Show one answer
SELECT p.name, COUNT(oi.product_id) AS lines FROM products p LEFT JOIN order_items oi ON oi.product_id = p.id GROUP BY p.id, p.name;
namelines
Basmati Rice 5 kg3
Toor Dal 1 kg3
Sunflower Oil 1 L3
Tea 500 g2
Notebook 200 pages4
Ball Pen pack of 104
Geometry Box2
School Bag2
LED Bulb 9 W3
Extension Board2
Earphones2
Power Bank2

16 rows

Showing the first 12 of 16 rows.

Exercise 2. Show the names of the customers who have no orders, in alphabetical order.

Solve it in the practice box

Show one answer
SELECT c.name FROM customers c LEFT JOIN orders o ON o.customer_id = c.id WHERE o.id IS NULL ORDER BY c.name;
name
Arjun Mehta
Meena Gill
Tara Singh

3 rows

Exercise 3. Show every customer's name and the date of their most recent order, with NULL for customers who have none. Sort by name.

Solve it in the practice box

Show one answer
SELECT c.name, MAX(o.order_date) FROM customers c LEFT JOIN orders o ON o.customer_id = c.id GROUP BY c.id, c.name ORDER BY c.name;
nameMAX(o.order_date)
Arjun MehtaNULL
Asha Verma2026-10-04
Imran Ali2026-06-21
Joy Thomas2026-06-02
Kabir Khan2026-09-01
Meena GillNULL
Neha Joshi2026-09-17
Priya Nair2026-08-11
Ravi Kumar2026-07-09
Sunita Rao2026-05-19
Tara SinghNULL
Vikram Das2026-04-18

12 rows

Examples on this page were run with SQLite 3.50.4 on 12 November 2026. The results shown are the real results.

This is an independent learning site. It is not connected with the makers of any database product. MySQL, PostgreSQL, SQL Server, Oracle and SQLite are named only so that you know where a piece of SQL works. The shop, its customers and its staff in the practice database are made up.