// Bases de données

Les jointures SQL expliquées (INNER, LEFT, RIGHT…)

Une base de données relationnelle répartit l’information dans plusieurs tables pour éviter les répétitions : les clients d’un côté, leurs commandes de l’autre. Les jointures (JOIN) servent à recombiner ces tables au moment de la lecture, en associant les lignes qui se correspondent. C’est l’opération la plus importante — et la plus souvent mal comprise — du langage SQL.

Ce guide couvre chaque type de jointure avec des exemples concrets. Pour illustrer, nous utiliserons deux tables tout au long de l’article.

-- Table clients
CREATE TABLE clients (
    id    INTEGER PRIMARY KEY,
    nom   TEXT,
    ville TEXT
);

INSERT INTO clients (id, nom, ville) VALUES
    (1, 'Dupont', 'Paris'),
    (2, 'Martin', 'Lyon'),
    (3, 'Bernard', 'Nantes');  -- ce client n'a aucune commande

-- Table commandes
CREATE TABLE commandes (
    id         INTEGER PRIMARY KEY,
    client_id  INTEGER,
    montant    NUMERIC
);

INSERT INTO commandes (id, client_id, montant) VALUES
    (100, 1, 250),
    (101, 1, 80),
    (102, 2, 120),
    (103, 4, 60);  -- client_id 4 n'existe pas dans clients

Retenez ces deux anomalies volontaires : le client Bernard (id 3) n’a passé aucune commande, et la commande 103 pointe vers un client_id inexistant. Ce sont elles qui révèlent les différences entre les types de jointure.

INNER JOIN : l’intersection

L’INNER JOIN ne conserve que les lignes ayant une correspondance dans les deux tables. C’est la jointure la plus courante et souvent celle qu’on veut par défaut.

SELECT clients.nom, commandes.montant
FROM clients
INNER JOIN commandes ON clients.id = commandes.client_id;

Résultat :

nom     | montant
--------+--------
Dupont  | 250
Dupont  | 80
Martin  | 120

Bernard n’apparaît pas (aucune commande), et la commande 103 non plus (aucun client correspondant). Seules les correspondances des deux côtés survivent. Le mot-clé INNER est optionnel : écrire simplement JOIN produit une jointure interne.

LEFT JOIN : toute la table de gauche

Le LEFT JOIN (ou LEFT OUTER JOIN) conserve toutes les lignes de la table de gauche, même sans correspondance à droite. Les colonnes de droite sont alors remplies de NULL.

SELECT clients.nom, commandes.montant
FROM clients
LEFT JOIN commandes ON clients.id = commandes.client_id;

Résultat :

nom     | montant
--------+--------
Dupont  | 250
Dupont  | 80
Martin  | 120
Bernard | NULL     <- conservé, sans commande

C’est la jointure idéale pour répondre à « tous mes clients, avec leurs commandes s’ils en ont ». Elle permet aussi de détecter les orphelins : pour trouver les clients sans aucune commande, filtrez sur le NULL.

SELECT clients.nom
FROM clients
LEFT JOIN commandes ON clients.id = commandes.client_id
WHERE commandes.id IS NULL;
-- Résultat : Bernard

RIGHT JOIN : toute la table de droite

Le RIGHT JOIN est le miroir du LEFT JOIN : il garde toutes les lignes de la table de droite, comblant la gauche avec des NULL si besoin.

SELECT clients.nom, commandes.id, commandes.montant
FROM clients
RIGHT JOIN commandes ON clients.id = commandes.client_id;

Résultat :

nom     | id  | montant
--------+-----+--------
Dupont  | 100 | 250
Dupont  | 101 | 80
Martin  | 102 | 120
NULL    | 103 | 60      <- commande orpheline

Ici, la commande 103 apparaît bien que son client n’existe pas. En pratique, on préfère souvent réécrire un RIGHT JOIN en LEFT JOIN en inversant l’ordre des tables, car cela se lit plus naturellement. Notez que MySQL supporte RIGHT JOIN, mais que SQLite ne l’a ajouté que récemment (version 3.39).

FULL JOIN : l’union complète

Le FULL JOIN (ou FULL OUTER JOIN) combine LEFT et RIGHT : il conserve toutes les lignes des deux tables, avec des NULL là où il n’y a pas de correspondance.

SELECT clients.nom, commandes.id, commandes.montant
FROM clients
FULL JOIN commandes ON clients.id = commandes.client_id;

Résultat :

nom     | id   | montant
--------+------+--------
Dupont  | 100  | 250
Dupont  | 101  | 80
Martin  | 102  | 120
Bernard | NULL | NULL    <- client sans commande
NULL    | 103  | 60      <- commande sans client

On récupère à la fois les clients sans commande et les commandes sans client. C’est la jointure de choix pour les audits de cohérence. Attention : MySQL ne connaît pas FULL JOIN ; on le simule par un UNION d’un LEFT et d’un RIGHT JOIN. PostgreSQL et SQLite récent le supportent nativement.

CROSS JOIN : le produit cartésien

Le CROSS JOIN associe chaque ligne de gauche à chaque ligne de droite, sans condition. Le nombre de lignes résultantes est le produit des deux tables. On l’utilise rarement, sauf pour générer des combinaisons (ex. toutes les tailles pour tous les modèles).

SELECT t.nom AS taille, c.nom AS couleur
FROM tailles t
CROSS JOIN couleurs c;
-- 3 tailles x 4 couleurs = 12 lignes

Un CROSS JOIN involontaire, obtenu en oubliant la condition ON, est une cause classique d’explosion du nombre de résultats.

Le self-join : joindre une table à elle-même

Un self-join joint une table avec elle-même, en lui donnant deux alias différents. C’est indispensable pour les données hiérarchiques, comme une table d’employés où chacun référence son manager.

CREATE TABLE employes (
    id         INTEGER PRIMARY KEY,
    nom        TEXT,
    manager_id INTEGER
);

INSERT INTO employes VALUES
    (1, 'Alice', NULL),   -- direction
    (2, 'Bob', 1),
    (3, 'Chloé', 1);

SELECT e.nom AS employe, m.nom AS manager
FROM employes e
LEFT JOIN employes m ON e.manager_id = m.id;

Résultat :

employe | manager
--------+--------
Alice   | NULL
Bob     | Alice
Chloé   | Alice

Le LEFT JOIN (plutôt qu’INNER) garantit qu’Alice, sans manager, reste dans le résultat.

Les jointures multiples

Rien n’empêche d’enchaîner plusieurs JOIN pour relier trois tables ou plus. Chaque jointure ajoute sa propre condition ON. Ajoutons une table produits liée aux commandes.

SELECT
    clients.nom,
    commandes.id      AS num_commande,
    produits.libelle  AS produit
FROM clients
INNER JOIN commandes ON clients.id = commandes.client_id
INNER JOIN lignes    ON commandes.id = lignes.commande_id
INNER JOIN produits  ON lignes.produit_id = produits.id;

Les jointures s’évaluent de gauche à droite. Un bon réflexe est de préfixer chaque colonne par le nom (ou l’alias) de sa table pour lever toute ambiguïté, surtout quand plusieurs tables ont une colonne id.

ON vs USING

La condition de jointure s’écrit habituellement avec ON, qui accepte n’importe quelle expression. Quand les deux colonnes portent exactement le même nom, USING offre un raccourci plus concis.

-- Avec ON (colonnes de noms différents)
SELECT * FROM clients
JOIN commandes ON clients.id = commandes.client_id;

-- Avec USING (nécessite une colonne de même nom des deux côtés)
SELECT * FROM commandes
JOIN lignes USING (commande_id);

Différence notable : USING fusionne la colonne commune en une seule dans le résultat, alors qu’ON la conserve en double (une par table). Utilisez ON dès que les noms diffèrent ou que la condition est complexe.

Les pièges à connaître

Les NULL ne se joignent jamais entre eux

En SQL, NULL = NULL ne vaut pas « vrai » mais « inconnu ». Deux lignes ayant NULL dans la colonne de jointure ne seront jamais associées par un ON. Ne comptez pas sur une jointure pour rapprocher des valeurs manquantes.

Les doublons multiplient les lignes

Si une clé apparaît plusieurs fois dans la table jointe, chaque correspondance produit une ligne. Un client avec 2 commandes ressort 2 fois. C’est correct, mais cela fausse les agrégats : une somme naïve peut compter deux fois la même donnée. Pensez à GROUP BY ou à DISTINCT quand c’est nécessaire.

-- Total commandé par client, sans double comptage
SELECT clients.nom, SUM(commandes.montant) AS total
FROM clients
LEFT JOIN commandes ON clients.id = commandes.client_id
GROUP BY clients.nom;

Filtrer un LEFT JOIN dans le WHERE l’annule

Placer une condition sur la table de droite dans le WHERE transforme discrètement un LEFT JOIN en INNER JOIN, car un NULL ne satisfait jamais la condition. Si vous voulez filtrer tout en gardant les lignes non appariées, mettez la condition dans le ON.

-- Garde tous les clients, ne prend que leurs commandes > 100
SELECT clients.nom, commandes.montant
FROM clients
LEFT JOIN commandes
    ON clients.id = commandes.client_id
    AND commandes.montant > 100;

En résumé

Le choix de la jointure dépend de la question posée. INNER JOIN pour les correspondances strictes, LEFT JOIN pour garder une table de référence complète (et repérer les orphelins), FULL JOIN pour les audits de cohérence. Méfiez-vous des trois pièges classiques : les NULL qui ne se joignent pas, les doublons qui gonflent les agrégats, et le filtre WHERE qui neutralise un LEFT JOIN. Avec ces repères, la plupart des requêtes multi-tables deviennent limpides.

À lire ensuite