SQL Quest
Sign in Français 0 XP Lvl 1
Intermediate

SQL LEFT JOIN: keep the rows without a match

5 min read · 4 runnable examples · updated October 9, 2026

With an INNER JOIN, a customer who never ordered disappears from the result. Yet sometimes that is exactly who you are looking for: inactive customers, products that never sold, suppliers without products… LEFT JOIN solves this problem: it keeps every row of the left table, whether it has a match or not.

Syntax

SELECT a.column, b.column
FROM table_a a
LEFT JOIN table_b b ON b.key = a.key;

How it works

Left and right

The “left” table is the one written after FROM; the “right” table is the one in the LEFT JOIN. Every row on the left is kept. When a left row has no match on the right, the right columns are NULL.

LEFT JOIN or INNER JOIN?

INNER JOINLEFT JOIN
Left row with a matchkeptkept
Left row without a matchremovedkept, with NULLs on the right

Finding what is missing

The most useful LEFT JOIN pattern: LEFT JOIN … WHERE right.id IS NULL. It only keeps the left rows without any match: customers without orders, products never sold, and so on.

RIGHT JOIN and FULL JOIN

RIGHT JOIN does the same thing while keeping the right table; you can always write it as a LEFT JOIN by swapping the tables. FULL OUTER JOIN keeps the rows of both sides. SQLite accepts them since version 3.39.

Diagram

clients
idprenom
1Léa
2Hugo
3Inès
commandes
idclient_id
101
111
122
result
prenomcommande
Léa10
Léa11
Hugo12
InèsNULL
clients LEFT JOIN commandes: Inès has no order, so she is kept with NULL in the order columns.

Simplified diagram to show the idea. The examples below use the real SQL Quest practice database (a fictional online shop: see the full schema).

Glossary: the sample database uses French names
clients
customers
commandes
orders
lignes_commande
order lines
produits
products
fournisseurs
suppliers
employes
employees
prenom / nom
first name / last name (or name)
ville
city
pays
country
prix
price
prix_unitaire
unit price
quantite
quantity
categorie
category
date_commande
order date
poste
job title
departement
department
salaire
salary
statut
status: livrée = delivered, expédiée = shipped, en cours = in progress, annulée = cancelled

Examples with their results

1. Keep every customer

Each customer with their number of orders, including those who have none (0).

SELECT c.id, c.prenom, c.nom, COUNT(o.id) AS nb_commandes
FROM clients c
LEFT JOIN commandes o ON o.client_id = c.id
GROUP BY c.id
ORDER BY nb_commandes
LIMIT 8;
Try it in the sandbox
Result
idprenomnomnb_commandes
111ManonRoux0
112AliceBernard0
113EmmaMoreau0
114NathanLefebvre0
115SarahSimon0
116KarimLefebvre0
117ThéoChevalier0
118ChloéLambert0

8 rows in total

2. Find the rows without a match

The customers who never placed an order.

SELECT c.id, c.prenom, c.nom, c.ville
FROM clients c
LEFT JOIN commandes o ON o.client_id = c.id
WHERE o.id IS NULL;
Try it in the sandbox
Result
idprenomnomville
111ManonRouxStrasbourg
112AliceBernardNantes
113EmmaMoreauBordeaux
114NathanLefebvreMarseille
115SarahSimonLyon
116KarimLefebvreNantes
117ThéoChevalierMontréal
118ChloéLambertNantes
119LucasBenaliBordeaux
120InèsLambertParis

10 rows in total

3. Compare with INNER JOIN

The number of customers obtained with each kind of join: the difference is the customers without orders.

SELECT
  (SELECT COUNT(DISTINCT c.id) FROM clients c JOIN commandes o ON o.client_id = c.id) AS avec_inner_join,
  (SELECT COUNT(DISTINCT c.id) FROM clients c LEFT JOIN commandes o ON o.client_id = c.id) AS avec_left_join;
Try it in the sandbox
Result
avec_inner_joinavec_left_join
110120

1 row in total

4. Employees who handled no orders

Management does not process orders: these employees have no match.

SELECT e.prenom, e.nom, e.poste
FROM employes e
LEFT JOIN commandes o ON o.employe_id = e.id
WHERE o.id IS NULL;
Try it in the sandbox
Result
prenomnomposte
ChloéDuboisPDG
JadeLefebvreDirecteur commercial
NathanRouxDirectrice technique
SamirSimonResponsable RH

4 rows in total

Common mistakes

Filtering the right table in the WHERE

A condition on the right table in the WHERE removes the rows where it is NULL: the LEFT JOIN then behaves like an INNER JOIN. Put that condition in the ON.

Avoid
SELECT c.prenom, o.id FROM clients c LEFT JOIN commandes o ON o.client_id = c.id WHERE o.statut = 'annulée';
Correct
SELECT c.prenom, o.id FROM clients c LEFT JOIN commandes o ON o.client_id = c.id AND o.statut = 'annulée';

Counting with COUNT(*)

For a customer without orders, the row still exists (with NULLs): COUNT(*) gives 1 instead of 0. Count a column from the right table.

Avoid
SELECT c.id, COUNT(*) FROM clients c LEFT JOIN commandes o ON o.client_id = c.id GROUP BY c.id;
Correct
SELECT c.id, COUNT(o.id) FROM clients c LEFT JOIN commandes o ON o.client_id = c.id GROUP BY c.id;

Swapping the tables

The table whose rows you want to keep must be on the left, right after FROM.

Avoid
SELECT c.nom FROM commandes o LEFT JOIN clients c ON c.id = o.client_id WHERE o.id IS NULL;
Correct
SELECT c.nom FROM clients c LEFT JOIN commandes o ON o.client_id = c.id WHERE o.id IS NULL;

Exercises

Try writing the query yourself before opening the solution.

Challenge 1. Show every supplier with their number of products, including those who have none.

Show a solution
SELECT f.nom, COUNT(p.id) AS nb_produits FROM fournisseurs f LEFT JOIN produits p ON p.fournisseur_id = f.id GROUP BY f.id;
nomnb_produits
TechNova1
ElectroPlus2
MaisonDeco4
SportZone13
BookWorld1
GreenFood5
AsiaParts3

7 rows in total

Try it in the sandbox

Challenge 2. Show every employee with the number of orders they handled, including those who handled none (0), from the smallest count to the largest.

Show a solution
SELECT e.prenom, e.nom, COUNT(o.id) AS nb_commandes FROM employes e LEFT JOIN commandes o ON o.employe_id = e.id GROUP BY e.id ORDER BY nb_commandes LIMIT 8;
prenomnomnb_commandes
ChloéDubois0
JadeLefebvre0
NathanRoux0
SamirSimon0
LouisGarcia18
LouisLambert20
InèsMoreau21
ChloéBenali22

8 rows in total

Try it in the sandbox
Want more practice?

The interactive exercises are checked automatically, with a tutor who explains your mistakes. A short lesson stays next to the editor.

Do the interactive exercises

Frequently asked questions

What is the difference between LEFT JOIN and INNER JOIN?

INNER JOIN only keeps the rows that have a match in both tables. LEFT JOIN keeps every row of the left table, and puts NULL in the right columns when there is no match.

Are LEFT JOIN and LEFT OUTER JOIN the same?

Yes, the word OUTER is optional.

Why does my LEFT JOIN return more rows than the left table?

Because a left row is repeated as many times as it has matches on the right. A customer with 5 orders appears 5 times.

How do I find customers without orders?

With LEFT JOIN commandes … WHERE commandes.id IS NULL, or with WHERE NOT EXISTS (SELECT 1 FROM commandes WHERE client_id = clients.id).

Read next