SQL Tutorial › Joins & Set Operators › LEFT JOIN

SQL LEFT JOIN

IntermediateLesson 33 of 6912 min read8 queries run on MySQL 8.4

LEFT JOIN (also LEFT OUTER JOIN) keeps every row of the left table, whether or not it has a match on the right. Where there is no match, the right table’s columns are NULL. That makes it the join for questions like “all customers, with their orders if they have any” and “which products have never been sold”.

Advertisement
In this lesson
  • Keep unmatched rows with LEFT JOIN
  • Find rows with no match using IS NULL
  • Count with zeros instead of losing rows
  • Avoid the WHERE clause that turns a LEFT JOIN into an INNER JOIN

LEFT JOIN syntax

MySQL
SELECT columnsFROM left_table lLEFT JOIN right_table r ON r.key = l.key;

Sample data

customers · 7 rows (some columns)
customer_idcustomer_namecity
1Ananya SharmaMumbai
2Rohit KumarDelhi
3Fatima KhanHyderabad
4David FernandesGoa
5Ishita BoseKolkata
6Manoj PillaiChennai
7Sara ThomasBengaluru
orders · 10 rows
order_idcustomer_idorder_datestatus
100112025-01-05Delivered
100222025-01-17Delivered
100312025-02-02Delivered
100432025-02-14Cancelled
100542025-03-08Delivered
100652025-03-22Shipped
100722025-04-10Delivered
100812025-04-28Pending
100932025-05-15Delivered
101052025-06-01Delivered

All customers, with their orders

SQL
SELECT c.customer_id, c.customer_name, o.order_id, o.statusFROM customers cLEFT JOIN orders o ON o.customer_id = c.customer_idORDER BY c.customer_id, o.order_id;
ResultMySQL 8.4
customer_idcustomer_nameorder_idstatus
1Ananya Sharma1001Delivered
1Ananya Sharma1003Delivered
1Ananya Sharma1008Pending
2Rohit Kumar1002Delivered
2Rohit Kumar1007Delivered
3Fatima Khan1004Cancelled
3Fatima Khan1009Delivered
4David Fernandes1005Delivered
5Ishita Bose1006Shipped
5Ishita Bose1010Delivered
6Manoj PillaiNULLNULL
7Sara ThomasNULLNULL
12 rows in set
How this query works, step by step
  1. customers is the left table, so all 7 customers will appear.
  2. Customers with orders get one row per order, as with an inner join.
  3. Manoj (6) and Sara (7) have no orders: they still appear once, with NULL in order_id and status.
  4. Result: 10 matched rows + 2 unmatched = 12 rows.
Advertisement

Find rows with no match (anti-join)

Keep only the rows where the right side is NULL. Customers who never ordered:

SQL
SELECT c.customer_id, c.customer_nameFROM customers cLEFT JOIN orders o ON o.customer_id = c.customer_idWHERE o.order_id IS NULL;
ResultMySQL 8.4
customer_idcustomer_name
6Manoj Pillai
7Sara Thomas
2 rows in set
How this query works, step by step
  1. The left join adds NULL order columns to customers without orders.
  2. WHERE o.order_id IS NULL keeps exactly those rows.
  3. Test a column that can never be NULL in a real match, such as the primary key order_id.

Products that have never been ordered:

SQL
SELECT p.product_id, p.product_nameFROM products pLEFT JOIN order_items oi ON oi.product_id = p.product_idWHERE oi.product_id IS NULL;
ResultMySQL 8.4
product_idproduct_name
9Monitor Arm
1 row in set

Departments with no employees:

SQL
SELECT d.dept_id, d.dept_nameFROM departments dLEFT JOIN employees e ON e.dept_id = d.dept_idWHERE e.emp_id IS NULL;
ResultMySQL 8.4
dept_iddept_name
5Finance
1 row in set

Counts that include zero

Orders per customer, including customers with none:

SQL
SELECT c.customer_name, COUNT(o.order_id) AS ordersFROM customers cLEFT JOIN orders o ON o.customer_id = c.customer_idGROUP BY c.customer_id, c.customer_nameORDER BY orders DESC, c.customer_name;
ResultMySQL 8.4
customer_nameorders
Ananya Sharma3
Fatima Khan2
Ishita Bose2
Rohit Kumar2
David Fernandes1
Manoj Pillai0
Sara Thomas0
7 rows in set
How this query works, step by step
  1. The left join keeps all 7 customers.
  2. COUNT(o.order_id) counts non-NULL order ids, so Manoj and Sara get 0.
  3. COUNT(*) would be wrong here: it counts their single NULL row as 1.
Advertisement

The WHERE trap

Goal: all customers, plus their delivered orders. Putting the status filter in WHERE silently removes the customers without orders:

SQL
SELECT c.customer_name, o.order_id, o.statusFROM customers cLEFT JOIN orders o ON o.customer_id = c.customer_idWHERE o.status = 'Delivered'ORDER BY c.customer_name;
ResultMySQL 8.4
customer_nameorder_idstatus
Ananya Sharma1001Delivered
Ananya Sharma1003Delivered
David Fernandes1005Delivered
Fatima Khan1009Delivered
Ishita Bose1010Delivered
Rohit Kumar1002Delivered
Rohit Kumar1007Delivered
7 rows in set
How this query works, step by step
  1. The left join produces NULL status for Manoj and Sara.
  2. WHERE o.status = 'Delivered' is UNKNOWN for NULL, so those rows are thrown away.
  3. The query now behaves like an inner join: the customers without delivered orders are gone.

Move the condition into ON, so it only decides which orders match:

MySQL
SELECT c.customer_name, o.order_id, o.statusFROM customers cLEFT JOIN orders o ON o.customer_id = c.customer_id AND o.status = 'Delivered'ORDER BY c.customer_name;
ResultMySQL 8.4
customer_nameorder_idstatus
Ananya Sharma1003Delivered
Ananya Sharma1001Delivered
David Fernandes1005Delivered
Fatima Khan1009Delivered
Ishita Bose1010Delivered
Manoj PillaiNULLNULL
Rohit Kumar1007Delivered
Rohit Kumar1002Delivered
Sara ThomasNULLNULL
9 rows in set
How this query works, step by step
  1. Only delivered orders can match now.
  2. Manoj and Sara have no delivered order, so they still appear once, with NULLs.
  3. Every customer is in the result.
Tip

Rule of thumb for outer joins: conditions on the right table go in ON; conditions on the left table go in WHERE.

LEFT JOIN with several tables

Every product with its total units sold, including unsold products:

SQL
SELECT p.product_name, COALESCE(SUM(oi.quantity), 0) AS units_soldFROM products pLEFT JOIN order_items oi ON oi.product_id = p.product_idGROUP BY p.product_id, p.product_nameORDER BY units_sold DESC, p.product_name;
ResultMySQL 8.4
product_nameunits_sold
Notebook Pack18
Gel Pens6
Desk Lamp3
Mechanical Keyboard3
Office Chair3
USB-C Hub2
Wireless Mouse2
Standing Desk1
Monitor Arm0
9 rows in set

COALESCE turns the NULL sum of the Monitor Arm into 0.

Advertisement

Exercise: Employees and departments

List all employees with their department name, including the CEO who has no department.

Expected resultMySQL 8.4
first_namedept_name
MeeraNULL
AshaEngineering
RahulEngineering
PriyaEngineering
KaranEngineering
VikramSales
NehaSales
ArjunSales
KavyaMarketing
RohanMarketing
SnehaHR
AmitEngineering
12 rows in set
Hint 1

employees must be the left table.

Exercise: Headcount including empty departments

For each department, show its name and the number of employees, including departments with no employees.

Expected resultMySQL 8.4
dept_namestaff
Engineering5
Sales3
Marketing2
HR1
Finance0
5 rows in set
Hint 1

Departments on the left, COUNT(e.emp_id).

Quick quiz

1. In A LEFT JOIN B, which rows are always kept?

2. How do you find rows in A with no match in B?

3. Why use COUNT(o.order_id) instead of COUNT(*) after a LEFT JOIN?

4. A WHERE condition on the right table of a LEFT JOIN…

Advertisement

LEFT JOIN FAQ

What is the difference between LEFT JOIN and LEFT OUTER JOIN?

None. OUTER is optional; both keep every row of the left table.

How do I find records that do not exist in another table?

Use LEFT JOIN other ON ... WHERE other.id IS NULL, or WHERE NOT EXISTS (SELECT 1 FROM other WHERE ...).

Why does my LEFT JOIN return the same result as an INNER JOIN?

Usually a WHERE condition on the right table removes the NULL rows. Move that condition into the ON clause.

Can a LEFT JOIN return more rows than the left table has?

Yes. If a left row matches several right rows, it appears once per match.