INNER JOIN ou LEFT JOIN : exemples SQL, NULL et erreurs courantes
Choisissez INNER JOIN pour ne garder que les lignes correspondantes. Choisissez LEFT JOIN pour conserver chaque ligne de la table de gauche, même sans correspondance à droite. Ce choix change les rapports clients, la recherche de données manquantes et les réponses aux entretiens.
Lorsqu’on affiche les clients et leurs commandes, un client ayant deux commandes apparaît deux fois. Une jointure retourne des combinaisons correspondantes, pas automatiquement une ligne par client. Un client sans commande disparaît avec INNER JOIN et reste avec LEFT JOIN, avec NULL dans les colonnes de commande.
| Question du rapport | Jointure adaptée |
|---|---|
| Quels clients ont des commandes ? | INNER JOIN |
| Afficher tous les clients, même sans commande | LEFT JOIN |
| Trouver les clients sans commande | LEFT JOIN puis test de NULL |
Les résultats ci-dessous montrent cette différence concrètement.
Essayez dans SQLite
Ouvrez SQL Lab, collez cette préparation et exécutez-la une fois. Lancez ensuite chaque requête séparément. Les noms des tables et des colonnes restent en anglais pour utiliser le même SQL dans toutes les langues. Le laboratoire et les leçons liées sont actuellement en anglais.
CREATE TABLE blog_customers (id INTEGER PRIMARY KEY, name TEXT);
CREATE TABLE blog_orders (
id INTEGER PRIMARY KEY, customer_id INTEGER, status TEXT, amount INTEGER
);
INSERT INTO blog_customers VALUES (1, 'Asha'), (2, 'Luis'), (3, 'Mei');
INSERT INTO blog_orders VALUES
(101, 1, 'paid', 120), (102, 1, 'pending', 80), (103, 2, 'paid', 60);
1. INNER JOIN retourne les correspondances
Asha a deux commandes, Luis une et Mei aucune. La requête retourne trois lignes. Mei est absente car aucune commande ne satisfait la condition de jointure.
SELECT c.name, o.id AS order_id, o.status
FROM blog_customers AS c
INNER JOIN blog_orders AS o ON o.customer_id = c.id
ORDER BY c.id, o.id;
Résultat attendu
| name | order_id | status |
|---|---|---|
| Asha | 101 | paid |
| Asha | 102 | pending |
| Luis | 103 | paid |
2. LEFT JOIN conserve les clients
Mei apparaît maintenant. Lorsqu’aucune commande ne correspond, les colonnes de droite contiennent NULL. Asha apparaît toujours deux fois : conserver les clients ne supprime pas les relations de type un-à-plusieurs.
SELECT c.name, o.id AS order_id, o.status
FROM blog_customers AS c
LEFT JOIN blog_orders AS o ON o.customer_id = c.id
ORDER BY c.id, o.id;
Résultat attendu
| name | order_id | status |
|---|---|---|
| Asha | 101 | paid |
| Asha | 102 | pending |
| Luis | 103 | paid |
| Mei | NULL | NULL |
NULL indique l’absence de valeur correspondante ; ce n’est ni zéro ni une chaîne vide.
3. Filtrer dans ON pour préserver les clients sans correspondance
Pour afficher chaque client avec ses seules commandes payées, placez la condition dans ON. Un client n’ayant que des commandes en attente serait aussi conservé, avec NULL dans les colonnes de commande. La condition détermine les commandes qui peuvent correspondre.
SELECT c.name, o.id AS order_id
FROM blog_customers AS c
LEFT JOIN blog_orders AS o
ON o.customer_id = c.id AND o.status = 'paid'
ORDER BY c.id, o.id;
Résultat attendu
| name | order_id |
|---|---|
| Asha | 101 |
| Luis | 103 |
| Mei | NULL |
4. WHERE peut supprimer les lignes à conserver
Déplacer cette condition dans WHERE change la question. WHERE filtre les lignes issues de la jointure. Le statut NULL de Mei ne satisfait pas status = 'paid', donc Mei disparaît. Cette erreur explique pourquoi un LEFT JOIN peut donner un résultat semblable à un INNER JOIN.
SELECT c.name, o.id AS order_id
FROM blog_customers AS c
LEFT JOIN blog_orders AS o ON o.customer_id = c.id
WHERE o.status = 'paid'
ORDER BY c.id, o.id;
Résultat attendu
| name | order_id |
|---|---|
| Asha | 101 |
| Luis | 103 |
5. Compter les commandes réelles, pas la ligne conservée
COUNT(o.id) ignore l’identifiant NULL de Mei et retourne zéro. COUNT(*) compterait la ligne du client conservée et retournerait un. L’identifiant de commande est une clé primaire non nulle : il convient donc pour compter les correspondances.
SELECT c.name, COUNT(o.id) AS order_count
FROM blog_customers AS c
LEFT JOIN blog_orders AS o ON o.customer_id = c.id
GROUP BY c.id, c.name
ORDER BY c.id;
Résultat attendu
| name | order_count |
|---|---|
| Asha | 2 |
| Luis | 1 |
| Mei | 0 |
Exercice et vérification
Trouvez les clients qui n’ont aucune commande payée, même s’ils ont des commandes en attente. Reprenez le LEFT JOIN avec la condition de paiement dans ON et ajoutez WHERE o.id IS NULL avant ORDER BY. Avec ces données, la réponse est Mei.
Testez NULL sur une clé non nulle de la table de droite. Un champ facultatif, tel qu’une note de livraison, peut être NULL même pour une commande existante. N’utilisez pas DISTINCT pour masquer les doublons sans comprendre la relation : la condition de jointure peut être incorrecte.
Pour obtenir une ligne par client, agrégez après la jointure, comme dans l’exemple de comptage. Pour un rapport par commande, conservez les lignes distinctes. Définissez ce que représente une ligne avant de choisir les colonnes.
Documentation et suite du parcours
Entraînez-vous avec les leçons INNER JOIN et LEFT/RIGHT JOIN. Ces exemples fonctionnent dans SQLite et utilisent une syntaxe également disponible dans PostgreSQL et MySQL. Les autres jointures et l’optimisation dépendent du moteur.
Essayez les exemples dans SQL Lab
Copiez la préparation et les requêtes de ce guide dans le laboratoire. Les exemples utilisent SQLite. Les leçons et les consignes du laboratoire sont en anglais.
Ouvrir SQL Lab