SQL LEFT JOIN : garder les lignes sans correspondance
Avec un INNER JOIN, un client qui n'a jamais commandé disparaît du résultat. Or c'est parfois justement lui qu'on cherche : les clients inactifs, les produits jamais vendus, les fournisseurs sans produit… LEFT JOIN résout ce problème : il garde toutes les lignes de la table de gauche, qu'elles aient une correspondance ou non.
Syntaxe
SELECT a.colonne, b.colonne
FROM table_a a
LEFT JOIN table_b b ON b.cle = a.cle;Comment ça marche
Gauche et droite
La table « de gauche » est celle écrite après FROM ; celle « de droite » est celle du LEFT JOIN. Toutes les lignes de gauche sont gardées. Quand une ligne de gauche n'a aucune correspondance à droite, les colonnes de droite valent NULL.
LEFT JOIN ou INNER JOIN ?
| INNER JOIN | LEFT JOIN | |
|---|---|---|
| Ligne de gauche avec correspondance | gardée | gardée |
| Ligne de gauche sans correspondance | supprimée | gardée, avec des NULL à droite |
Trouver ce qui manque
Le motif le plus utile du LEFT JOIN : LEFT JOIN … WHERE droite.id IS NULL. Il ne garde que les lignes de gauche sans aucune correspondance : les clients sans commande, les produits jamais vendus, etc.
RIGHT JOIN et FULL JOIN
RIGHT JOIN fait la même chose en gardant la table de droite ; on peut toujours l'écrire comme un LEFT JOIN en inversant les tables. FULL OUTER JOIN garde les lignes des deux côtés. SQLite les accepte depuis sa version 3.39.
Schéma
| id | prenom |
|---|---|
| 1 | Léa |
| 2 | Hugo |
| 3 | Inès |
| id | client_id |
|---|---|
| 10 | 1 |
| 11 | 1 |
| 12 | 2 |
| prenom | commande |
|---|---|
| Léa | 10 |
| Léa | 11 |
| Hugo | 12 |
| Inès | NULL |
Schéma simplifié pour illustrer le principe. Les exemples ci-dessous utilisent la vraie base d'exercices de SQL Quest (une boutique en ligne fictive : voir le schéma complet).
Exemples avec leurs résultats
1. Garder tous les clients
Chaque client avec son nombre de commandes, y compris ceux qui n'en ont aucune (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; Essayer dans le bac à sable| id | prenom | nom | nb_commandes |
|---|---|---|---|
| 111 | Manon | Roux | 0 |
| 112 | Alice | Bernard | 0 |
| 113 | Emma | Moreau | 0 |
| 114 | Nathan | Lefebvre | 0 |
| 115 | Sarah | Simon | 0 |
| 116 | Karim | Lefebvre | 0 |
| 117 | Théo | Chevalier | 0 |
| 118 | Chloé | Lambert | 0 |
8 lignes au total
2. Trouver les lignes sans correspondance
Les clients qui n'ont jamais passé de commande.
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; Essayer dans le bac à sable| id | prenom | nom | ville |
|---|---|---|---|
| 111 | Manon | Roux | Strasbourg |
| 112 | Alice | Bernard | Nantes |
| 113 | Emma | Moreau | Bordeaux |
| 114 | Nathan | Lefebvre | Marseille |
| 115 | Sarah | Simon | Lyon |
| 116 | Karim | Lefebvre | Nantes |
| 117 | Théo | Chevalier | Montréal |
| 118 | Chloé | Lambert | Nantes |
| 119 | Lucas | Benali | Bordeaux |
| 120 | Inès | Lambert | Paris |
10 lignes au total
3. Comparer avec INNER JOIN
Le nombre de clients obtenu avec chaque type de jointure : la différence correspond aux clients sans commande.
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; Essayer dans le bac à sable| avec_inner_join | avec_left_join |
|---|---|
| 110 | 120 |
1 ligne au total
4. Les employés qui n'ont géré aucune commande
La direction ne traite pas de commandes : ces employés n'ont aucune correspondance.
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; Essayer dans le bac à sable| prenom | nom | poste |
|---|---|---|
| Chloé | Dubois | PDG |
| Jade | Lefebvre | Directeur commercial |
| Nathan | Roux | Directrice technique |
| Samir | Simon | Responsable RH |
4 lignes au total
Les erreurs fréquentes
Filtrer la table de droite dans le WHERE
Une condition sur la table de droite dans le WHERE élimine les lignes où elle vaut NULL : le LEFT JOIN se comporte alors comme un INNER JOIN. Placez cette condition dans le ON.
SELECT c.prenom, o.id FROM clients c LEFT JOIN commandes o ON o.client_id = c.id WHERE o.statut = 'annulée';SELECT c.prenom, o.id FROM clients c LEFT JOIN commandes o ON o.client_id = c.id AND o.statut = 'annulée';Compter avec COUNT(*)
Pour un client sans commande, la ligne existe quand même (avec des NULL) : COUNT(*) donne 1 au lieu de 0. Comptez une colonne de droite.
SELECT c.id, COUNT(*) FROM clients c LEFT JOIN commandes o ON o.client_id = c.id GROUP BY c.id;SELECT c.id, COUNT(o.id) FROM clients c LEFT JOIN commandes o ON o.client_id = c.id GROUP BY c.id;Inverser les tables
La table dont on veut garder toutes les lignes doit être à gauche, juste après FROM.
SELECT c.nom FROM commandes o LEFT JOIN clients c ON c.id = o.client_id WHERE o.id IS NULL;SELECT c.nom FROM clients c LEFT JOIN commandes o ON o.client_id = c.id WHERE o.id IS NULL;Exercices
Essayez d'écrire la requête vous-même avant d'ouvrir la solution.
Défi 1. Affichez tous les fournisseurs avec leur nombre de produits, y compris ceux qui n'en ont aucun.
Voir une 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;| nom | nb_produits |
|---|---|
| TechNova | 1 |
| ElectroPlus | 2 |
| MaisonDeco | 4 |
| SportZone | 13 |
| BookWorld | 1 |
| GreenFood | 5 |
| AsiaParts | 3 |
7 lignes au total
Essayer dans le bac à sableDéfi 2. Affichez tous les employés avec le nombre de commandes qu'ils ont gérées, y compris ceux qui n'en ont géré aucune (0), du plus petit nombre au plus grand.
Voir une 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;| prenom | nom | nb_commandes |
|---|---|---|
| Chloé | Dubois | 0 |
| Jade | Lefebvre | 0 |
| Nathan | Roux | 0 |
| Samir | Simon | 0 |
| Louis | Garcia | 18 |
| Louis | Lambert | 20 |
| Inès | Moreau | 21 |
| Chloé | Benali | 22 |
8 lignes au total
Essayer dans le bac à sableLes exercices interactifs sont corrigés automatiquement, avec un prof qui explique vos erreurs. Une leçon résumée reste affichée à côté de l'éditeur.
Questions fréquentes
Quelle est la différence entre LEFT JOIN et INNER JOIN ?
INNER JOIN ne garde que les lignes qui ont une correspondance dans les deux tables. LEFT JOIN garde toutes les lignes de la table de gauche, et met NULL dans les colonnes de droite quand il n'y a pas de correspondance.
LEFT JOIN et LEFT OUTER JOIN, c'est pareil ?
Oui, le mot OUTER est facultatif.
Pourquoi mon LEFT JOIN renvoie-t-il plus de lignes que la table de gauche ?
Parce qu'une ligne de gauche est répétée autant de fois qu'elle a de correspondances à droite. Un client avec 5 commandes apparaît 5 fois.
Comment trouver les clients sans commande ?
Avec LEFT JOIN commandes … WHERE commandes.id IS NULL, ou avec WHERE NOT EXISTS (SELECT 1 FROM commandes WHERE client_id = clients.id).