Les jointures SQL font peur à beaucoup de débutants… alors qu’elles reposent sur une idée toute simple : relier les lignes de deux tables grâce à une colonne commune. Dans ce guide, tu vas comprendre INNER JOIN, LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN, CROSS JOIN et SELF JOIN avec des schémas, des résultats ligne par ligne et 10 exercices corrigés.

🐬 MySQL / MariaDB🧠 Schémas visuels✍️ 10 exercices corrigés🎯 Débutant → intermédiaire⏱️ 20 min de lecture

Tu as une table clients et une table commandes. Comment afficher le nom de chaque client à côté de ses commandes ? Comment trouver les clients qui n’ont jamais commandé ? Comment repérer des commandes « orphelines » ? Toutes ces questions ont la même réponse : les jointures SQL.

Une jointure (JOIN) combine les lignes de plusieurs tables à partir d’une condition, le plus souvent l’égalité entre une clé étrangère et une clé primaire. C’est la base de toute base de données relationnelle : sans jointures, impossible d’exploiter correctement des données bien normalisées.

Jointures SQL expliquées : INNER JOIN, LEFT JOIN, RIGHT JOIN avec schémas
Les jointures SQL expliquées visuellement : un schéma, une requête, un résultat.
💡
Ce que tu sauras faire à la fin

Choisir la bonne jointure en 5 secondes, éviter les doublons et les NULL surprises, simuler un FULL OUTER JOIN en MySQL, faire une anti-jointure, joindre 3 tables et plus, et écrire des jointures rapides grâce aux index.

Le principe d’une jointure SQL

Dans une base relationnelle, on évite de répéter l’information. Plutôt que d’écrire le nom et la ville du client dans chaque commande, la table commandes contient seulement client_id, qui pointe vers clients.id. La jointure SQL reconstitue l’information au moment de la requête.

La syntaxe générale est toujours la même :

SELECT colonnes
FROM table_a AS a
[INNER | LEFT | RIGHT | CROSS] JOIN table_b AS b
    ON a.colonne = b.colonne
WHERE ...;
Syntaxe générale d’une jointure
  • FROM désigne la table de gauche (A) ;
  • JOIN désigne la table de droite (B) ;
  • ON indique la condition de jointure, c’est-à-dire comment relier les lignes ;
  • les alias (c, o) rendent la requête lisible et évitent les ambiguïtés de colonnes.

Les tables de l’exemple pour tester les jointures SQL

Tous les exemples de ce tutoriel utilisent ces deux petites tables. Regarde bien les pièges glissés volontairement : David n’a aucune commande, et la commande 104 pointe vers un client 9 qui n’existe pas.

Tables clients et commandes pour comprendre les jointures SQL
Les tables clients et commandes, reliées par commandes.client_id → clients.id.

Copie ce script dans phpMyAdmin, MySQL Workbench ou DBeaver pour tester chaque requête en même temps que tu lis :

CREATE TABLE clients (
    id    INT PRIMARY KEY,
    nom   VARCHAR(50) NOT NULL,
    ville VARCHAR(50) NOT NULL
);

CREATE TABLE commandes (
    id        INT PRIMARY KEY,
    client_id INT NOT NULL,
    total     DECIMAL(8,2) NOT NULL,
    INDEX idx_commandes_client (client_id)
);

INSERT INTO clients VALUES
    (1, 'Alice', 'Liège'), (2, 'Bruno', 'Namur'),
    (3, 'Chloé', 'Bruxelles'), (4, 'David', 'Mons');

INSERT INTO commandes VALUES
    (101, 1, 120.00), (102, 1, 45.00),
    (103, 3, 80.00),  (104, 9, 60.00);
jointures-demo.sql
🛠️
Pas encore d’outil pour tester ?

Consulte notre sélection des 10 meilleurs outils de gestion de bases de données : DBeaver, HeidiSQL ou phpMyAdmin font parfaitement l’affaire.

INNER JOIN : la jointure SQL des correspondances

INNER JOIN en SQL : schéma de Venn de l'intersection entre deux tables
INNER JOIN : on ne garde que l’intersection, les lignes présentes des deux côtés.

INNER JOIN est la jointure la plus utilisée. Elle ne renvoie que les lignes pour lesquelles la condition ON est vraie des deux côtés. Le mot INNER est d’ailleurs facultatif : un simple JOIN est un INNER JOIN.

SELECT c.nom, o.id AS commande, o.total
FROM clients AS c
INNER JOIN commandes AS o
    ON o.client_id = c.id;
INNER JOIN
nomcommandetotal
Alice101120.00
Alice10245.00
Chloé10380.00

Remarque trois choses :

  • Alice apparaît deux fois : une jointure duplique la ligne de gauche autant de fois qu’il y a de correspondances à droite. C’est normal (relation un-à-plusieurs).
  • Bruno et David disparaissent : ils n’ont aucune commande.
  • La commande 104 disparaît aussi : son client 9 n’existe pas.
🎯
Quand l’utiliser ?

Quand tu veux uniquement les données complètes : les commandes avec leur client, les articles avec leur catégorie, les messages avec leur auteur.

LEFT JOIN : la jointure SQL qui garde toute la table de gauche

LEFT JOIN en SQL : toutes les lignes de la table A et les correspondances de B
LEFT JOIN : toute la table de gauche, complétée par les correspondances de droite (ou NULL).

LEFT JOIN (ou LEFT OUTER JOIN) garde toutes les lignes de la table de gauche, même sans correspondance. Les colonnes de la table de droite valent alors NULL.

SELECT c.nom, o.id AS commande, o.total
FROM clients AS c
LEFT JOIN commandes AS o
    ON o.client_id = c.id;
LEFT JOIN
nomcommandetotal
Alice101120.00
Alice10245.00
BrunoNULLNULL
Chloé10380.00
DavidNULLNULL

C’est la jointure idéale pour les rapports : « tous les clients avec leur chiffre d’affaires, même ceux à zéro ». Combine-la avec COALESCE pour remplacer le NULL par 0 :

SELECT c.nom,
       COUNT(o.id)               AS nb_commandes,
       COALESCE(SUM(o.total), 0) AS chiffre_affaires
FROM clients AS c
LEFT JOIN commandes AS o ON o.client_id = c.id
GROUP BY c.id, c.nom
ORDER BY chiffre_affaires DESC;
Chiffre d’affaires par client
⚠️
Piège n°1 : COUNT(*) au lieu de COUNT(o.id)

COUNT(*) compte les lignes, y compris celles remplies de NULL : Bruno aurait « 1 commande ». COUNT(o.id) ignore les NULL et renvoie bien 0.

RIGHT JOIN : la jointure SQL miroir

RIGHT JOIN en SQL : toutes les lignes de la table B et les correspondances de A
RIGHT JOIN : le miroir du LEFT JOIN.

RIGHT JOIN fait exactement l’inverse : il garde toutes les lignes de la table de droite. Ici, il fait apparaître la commande orpheline :

SELECT c.nom, o.id AS commande, o.total
FROM clients AS c
RIGHT JOIN commandes AS o
    ON o.client_id = c.id;
RIGHT JOIN
nomcommandetotal
Alice101120.00
Alice10245.00
Chloé10380.00
NULL10460.00
✍️
Astuce de lisibilité

Dans la pratique, la plupart des développeurs n’écrivent jamais de RIGHT JOIN : ils inversent l’ordre des tables et écrivent un LEFT JOIN. A RIGHT JOIN B = B LEFT JOIN A. Une requête qui se lit toujours « de gauche à droite » est plus facile à relire.

FULL OUTER JOIN : la jointure SQL complète (et comment faire en MySQL)

FULL OUTER JOIN en SQL et son équivalent MySQL avec UNION
FULL OUTER JOIN : toutes les lignes de A et toutes celles de B.

FULL OUTER JOIN renvoie toutes les lignes des deux tables, avec des NULL là où il n’y a pas de correspondance. PostgreSQL, SQL Server et Oracle le supportent… mais MySQL et MariaDB, non. On le simule en combinant un LEFT JOIN et un RIGHT JOIN avec UNION :

SELECT c.nom, o.id AS commande, o.total
FROM clients AS c
LEFT JOIN commandes AS o ON o.client_id = c.id

UNION

SELECT c.nom, o.id, o.total
FROM clients AS c
RIGHT JOIN commandes AS o ON o.client_id = c.id;
FULL OUTER JOIN en MySQL
nomcommandetotal
Alice101120.00
Alice10245.00
BrunoNULLNULL
Chloé10380.00
DavidNULLNULL
NULL10460.00

UNION supprime les doublons (les correspondances présentes dans les deux requêtes). Sur de gros volumes, UNION ALL est plus rapide, mais il faut alors exclure les correspondances de la seconde requête avec WHERE c.id IS NULL.

LEFT JOIN … IS NULL : la jointure SQL pour trouver ce qui manque

Anti-jointure SQL : LEFT JOIN avec WHERE IS NULL pour trouver les lignes sans correspondance
L’anti-jointure : les lignes de A qui n’ont aucune correspondance dans B.

C’est la jointure la plus utile au quotidien, et la moins connue. En ajoutant WHERE b.id IS NULL à un LEFT JOIN, tu obtiens les lignes de gauche sans correspondance : clients sans commande, articles jamais vendus, utilisateurs inactifs…

SELECT c.id, c.nom
FROM clients AS c
LEFT JOIN commandes AS o ON o.client_id = c.id
WHERE o.id IS NULL;
Clients qui n’ont jamais commandé
idnom
2Bruno
4David

La même logique, inversée, détecte les données orphelines (commandes dont le client a été supprimé) :

SELECT o.*
FROM commandes AS o
LEFT JOIN clients AS c ON c.id = o.client_id
WHERE c.id IS NULL;
Commandes orphelines
🔒
Comment éviter les orphelins ?

Déclare une vraie clé étrangère : FOREIGN KEY (client_id) REFERENCES clients(id) ON DELETE RESTRICT. MySQL refusera alors l’insertion de la commande 104 (avec le moteur InnoDB).

Alternative équivalente avec NOT EXISTS, souvent tout aussi rapide et parfois plus lisible :

SELECT c.id, c.nom
FROM clients AS c
WHERE NOT EXISTS (
    SELECT 1 FROM commandes AS o WHERE o.client_id = c.id
);
Anti-jointure avec NOT EXISTS
⚠️
Évite NOT IN avec une sous-requête

WHERE c.id NOT IN (SELECT client_id FROM commandes) renvoie zéro ligne dès que la sous-requête contient un seul NULL. Préfère toujours NOT EXISTS ou LEFT JOIN … IS NULL.

CROSS JOIN : la jointure SQL de toutes les combinaisons

CROSS JOIN en SQL : produit cartésien entre deux tables
CROSS JOIN : chaque ligne de A est combinée avec chaque ligne de B.

CROSS JOIN n’a pas de condition : il produit le produit cartésien, c’est-à-dire toutes les combinaisons possibles. 4 tailles × 5 couleurs = 20 lignes. C’est parfait pour générer un catalogue de variantes, un calendrier ou une grille de planning :

SELECT t.taille, c.couleur, CONCAT('TS-', t.taille, '-', c.couleur) AS sku
FROM tailles AS t
CROSS JOIN couleurs AS c
ORDER BY t.ordre, c.couleur;
Générer toutes les variantes d’un t-shirt
💥
Attention au produit cartésien accidentel

Oublier la clause ON (ou écrire FROM a, b sans WHERE) produit un CROSS JOIN involontaire : 10 000 clients × 50 000 commandes = 500 millions de lignes. Si ta requête renvoie soudain des millions de résultats, vérifie tes conditions de jointure.

SELF JOIN : joindre une table avec elle-même

Un SELF JOIN n’est pas un mot-clé, mais une technique : on joint une table à elle-même grâce à deux alias différents. Exemple classique, une table d’employés où chaque employé a un responsable :

SELECT e.nom AS employe,
       r.nom AS responsable
FROM employes AS e
LEFT JOIN employes AS r ON r.id = e.responsable_id
ORDER BY responsable, employe;
Employés et leur responsable

Le LEFT JOIN permet de garder la directrice, qui n’a pas de responsable (responsable_id à NULL). On utilise aussi le self join pour comparer des lignes entre elles : produits plus chers que la moyenne de leur catégorie, mesures consécutives d’un capteur, doublons dans une table…

Jointures SQL sur 3 tables ou plus

Les jointures s’enchaînent naturellement. Ajoutons une table lignes_commande et une table produits pour obtenir le détail complet d’une commande :

SELECT c.nom        AS client,
       o.id         AS commande,
       p.libelle    AS produit,
       l.quantite,
       l.quantite * p.prix AS sous_total
FROM commandes AS o
INNER JOIN clients         AS c ON c.id = o.client_id
INNER JOIN lignes_commande AS l ON l.commande_id = o.id
INNER JOIN produits        AS p ON p.id = l.produit_id
WHERE o.id = 101;
Jointure sur 4 tables

Une règle simple : une jointure par relation, chacune avec sa propre clause ON. Si tu mélanges INNER et LEFT JOIN, sois attentif : un INNER JOIN placé après un LEFT JOIN peut annuler l’effet de ce dernier en éliminant les lignes NULL.

ON ou WHERE : la différence qui change tout

Avec un INNER JOIN, placer une condition dans ON ou dans WHERE donne le même résultat. Avec un LEFT JOIN, ce n’est plus vrai :

-- ❌ Transforme le LEFT JOIN en INNER JOIN : Bruno et David disparaissent
SELECT c.nom, o.total
FROM clients AS c
LEFT JOIN commandes AS o ON o.client_id = c.id
WHERE o.total > 50;
Condition dans WHERE (piège)
-- ✅ Tous les clients, avec seulement leurs commandes de plus de 50 €
SELECT c.nom, o.total
FROM clients AS c
LEFT JOIN commandes AS o
    ON o.client_id = c.id
   AND o.total > 50;
Condition dans ON (correct)

Pourquoi ? Le WHERE s’applique après la jointure : pour Bruno, o.total vaut NULL, et NULL > 50 n’est pas vrai, donc la ligne est filtrée. Dans le ON, la condition sert seulement à choisir quelles commandes associer.

Des jointures SQL rapides grâce aux index

Une jointure entre deux grosses tables peut être très lente si MySQL doit, pour chaque client, parcourir toute la table des commandes. La solution : un index sur la colonne de jointure (ici commandes.client_id). Les clés primaires sont indexées automatiquement, les clés étrangères InnoDB aussi, mais pas une simple colonne INT.

CREATE INDEX idx_commandes_client ON commandes (client_id);

-- Vérifier que l'index est utilisé
EXPLAIN SELECT c.nom, o.total
FROM clients AS c
JOIN commandes AS o ON o.client_id = c.id;
Indexer la colonne de jointure
⚡
Pour aller plus loin

Lire un plan d’exécution EXPLAIN, choisir entre index simple, composite et couvrant : tout est détaillé dans notre guide Optimiser une requête MySQL lente.

Aide-mémoire des jointures SQL

Aide-mémoire des jointures SQL : INNER, LEFT, RIGHT, FULL OUTER, anti-jointure et CROSS JOIN
L’aide-mémoire à garder sous la main : quelle jointure pour quel résultat.
Je veux…Jointure SQL
Seulement les lignes liées des deux côtésINNER JOIN
Toutes les lignes de A, liées ou nonLEFT JOIN
Toutes les lignes de B, liées ou nonRIGHT JOIN (ou LEFT JOIN inversé)
Tout A et tout BFULL OUTER JOIN (MySQL : LEFT UNION RIGHT)
Les lignes de A sans correspondanceLEFT JOIN … WHERE b.id IS NULL
Toutes les combinaisonsCROSS JOIN
Relier une table à elle-mêmeSELF JOIN (deux alias)

10 exercices corrigés sur les jointures SQL

Rien ne vaut la pratique. Utilise les tables clients et commandes créées plus haut, écris ta requête, puis déplie la correction. Les exercices sont classés du plus simple au plus difficile.

Exercice 1 — Afficher chaque commande avec le nom et la ville du client.
SELECT o.id, o.total, c.nom, c.ville
FROM commandes AS o
INNER JOIN clients AS c ON c.id = o.client_id;

Résultat : 3 lignes : la commande 104 n’a pas de client.

Exercice 2 — Lister tous les clients, même ceux sans commande.
SELECT c.nom, o.id AS commande
FROM clients AS c
LEFT JOIN commandes AS o ON o.client_id = c.id;

Résultat : 5 lignes, dont Bruno et David avec NULL.

Exercice 3 — Trouver les clients qui n’ont jamais commandé.
SELECT c.nom
FROM clients AS c
LEFT JOIN commandes AS o ON o.client_id = c.id
WHERE o.id IS NULL;

Résultat : Bruno et David.

Exercice 4 — Trouver les commandes dont le client n’existe pas.
SELECT o.id
FROM commandes AS o
LEFT JOIN clients AS c ON c.id = o.client_id
WHERE c.id IS NULL;

Résultat : La commande 104.

Exercice 5 — Nombre de commandes par client, zéro compris.
SELECT c.nom, COUNT(o.id) AS nb
FROM clients AS c
LEFT JOIN commandes AS o ON o.client_id = c.id
GROUP BY c.id, c.nom;

Résultat : Alice 2, Bruno 0, Chloé 1, David 0. Attention à COUNT(o.id) et non COUNT(*).

Exercice 6 — Chiffre d’affaires total par ville.
SELECT c.ville, COALESCE(SUM(o.total), 0) AS ca
FROM clients AS c
LEFT JOIN commandes AS o ON o.client_id = c.id
GROUP BY c.ville
ORDER BY ca DESC;

Résultat : Liège 165, Bruxelles 80, Namur 0, Mons 0.

Exercice 7 — Clients ayant dépensé plus de 100 € au total.
SELECT c.nom, SUM(o.total) AS total
FROM clients AS c
INNER JOIN commandes AS o ON o.client_id = c.id
GROUP BY c.id, c.nom
HAVING SUM(o.total) > 100;

Résultat : Alice (165 €). HAVING filtre après le regroupement, WHERE avant.

Exercice 8 — Tous les clients, avec seulement leurs commandes de plus de 50 €.
SELECT c.nom, o.id, o.total
FROM clients AS c
LEFT JOIN commandes AS o
    ON o.client_id = c.id
   AND o.total > 50;

Résultat : La condition va dans ON, sinon Bruno et David disparaissent.

Exercice 9 — Simuler un FULL OUTER JOIN en MySQL.
SELECT c.nom, o.id FROM clients c LEFT JOIN commandes o ON o.client_id = c.id
UNION
SELECT c.nom, o.id FROM clients c RIGHT JOIN commandes o ON o.client_id = c.id;

Résultat : 6 lignes : 4 clients (Alice deux fois) + la commande orpheline.

Exercice 10 — Afficher la plus grosse commande de chaque client.
SELECT c.nom, o.id, o.total
FROM clients AS c
JOIN commandes AS o ON o.client_id = c.id
LEFT JOIN commandes AS o2
    ON o2.client_id = o.client_id
   AND o2.total > o.total
WHERE o2.id IS NULL;

Résultat : Alice 101 (120 €), Chloé 103 (80 €). Un self join + anti-jointure : on garde la commande pour laquelle aucune autre commande du même client n’est plus chère.

Les erreurs courantes avec les jointures SQL

SymptômeCause probableSolution
Des lignes en doubleRelation un-à-plusieurs, ou jointure sur une colonne non uniqueVérifie la condition ON, regroupe avec GROUP BY, ou utilise EXISTS
Des millions de lignesProduit cartésien : condition ON oubliéeUne clause ON par jointure
Le LEFT JOIN se comporte comme un INNER JOINCondition sur la table de droite dans le WHEREDéplace la condition dans le ON
« Column ‘id’ in field list is ambiguous »Deux tables ont une colonne du même nomPréfixe avec l’alias : c.id
Requête très lentePas d’index sur la colonne de jointureCREATE INDEX + vérification avec EXPLAIN
NOT IN ne renvoie rienUn NULL dans la sous-requêteUtilise NOT EXISTS

FAQ sur les jointures SQL

Quelle est la différence entre JOIN et INNER JOIN ?

Aucune : en SQL, JOIN seul est un raccourci pour INNER JOIN. Les deux ne renvoient que les lignes qui ont une correspondance dans les deux tables.

Quelle est la différence entre INNER JOIN et LEFT JOIN ?

INNER JOIN ne garde que les lignes présentes des deux côtés. 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.

MySQL supporte-t-il le FULL OUTER JOIN ?

Non, ni MySQL ni MariaDB. On le simule en combinant un LEFT JOIN et un RIGHT JOIN avec UNION.

Pourquoi ma jointure SQL renvoie-t-elle des doublons ?

Parce qu’une ligne de gauche correspond à plusieurs lignes de droite (relation un-à-plusieurs) ou parce que la condition ON n’est pas assez précise. Regroupe avec GROUP BY ou utilise EXISTS si tu veux seulement tester l’existence.

Faut-il préférer LEFT JOIN ou RIGHT JOIN ?

LEFT JOIN, par convention : une requête qui se lit de gauche à droite est plus claire. Tout RIGHT JOIN peut s’écrire en LEFT JOIN en inversant l’ordre des tables.

Les jointures SQL sont-elles lentes ?

Non, à condition que les colonnes de jointure soient indexées. Sans index, MySQL doit parcourir toute la table à chaque correspondance ; avec un index, il trouve les lignes en quelques sauts.

Conclusion

Les jointures SQL se résument à une question : quelles lignes veux-tu garder quand il n’y a pas de correspondance ? Aucune → INNER JOIN. Celles de gauche → LEFT JOIN. Celles de droite → RIGHT JOIN. Toutes → FULL OUTER JOIN. Et pour trouver ce qui manque, l’anti-jointure LEFT JOIN … IS NULL deviendra vite ton réflexe.

Pour mettre tout ça en pratique dans un vrai projet, découvre nos tutoriels application de gestion de stock en PHP/MySQL et application de planning en PHP et MySQL, qui utilisent des jointures à chaque écran. La documentation officielle de MySQL sur la syntaxe JOIN reste la référence pour les cas avancés.

Comments

No comments yet. Why don’t you start the discussion?

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *