Les JOIN SQL combinent les lignes de deux tables en faisant correspondre une colonne qu’elles partagent, comme le author_id d’un livre et l’id d’un auteur. Le type de jointure décide du sort des lignes sans correspondance. Les exemples fonctionnent sous PostgreSQL, MySQL, SQLite et SQL Server.
Comment utiliser les JOIN SQL
Place la deuxième table après JOIN, et indique dans ON comment ses lignes correspondent à celles de la première :
SELECT columns
FROM table_a
JOIN table_b ON table_a.key = table_b.key;
Tous les exemples de cette section joignent les deux mêmes tables : Butler a écrit deux des livres, Asimov aucun, et personne ne sait qui a écrit Beowulf.
INNER JOIN
Un INNER JOIN ne renvoie que les lignes qui correspondent dans les deux tables. JOIN seul équivaut à INNER JOIN.
-- Input: authors
-- id | name
-- ---+--------
-- 1 | Le Guin
-- 2 | Butler
-- 3 | Asimov
-- Input: books
-- title | author_id
-- ---------------------+----------
-- The Dispossessed | 1
-- Kindred | 2
-- Parable of the Sower | 2
-- Beowulf | NULL
SELECT a.name, b.title
FROM authors a
INNER JOIN books b ON a.id = b.author_id;
-- Output:
-- name | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler | Kindred
-- Butler | Parable of the Sower
Asimov n’a aucun livre et Beowulf n’a pas d’auteur, donc aucun des deux n’apparaît.
LEFT JOIN
Un LEFT JOIN (ou LEFT OUTER JOIN) renvoie toutes les lignes de la table de gauche, celle qui suit FROM. Quand la table de droite n’a pas de correspondance, ses colonnes valent NULL.
-- Input: the authors and books tables above
SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id;
-- Output:
-- name | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler | Kindred
-- Butler | Parable of the Sower
-- Asimov | NULL
RIGHT JOIN
Un RIGHT JOIN (ou RIGHT OUTER JOIN) en est le miroir : toutes les lignes de la table de droite, avec des NULL là où la table de gauche n’a pas de correspondance.
-- Input: the authors and books tables above
SELECT a.name, b.title
FROM authors a
RIGHT JOIN books b ON a.id = b.author_id;
-- Output:
-- name | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler | Kindred
-- Butler | Parable of the Sower
-- NULL | Beowulf
FULL OUTER JOIN
Un FULL OUTER JOIN renvoie toutes les lignes des deux tables, associées quand c’est possible et complétées par des NULL sinon. MySQL ne le propose pas : vois « Syntaxe alternative » plus bas.
-- Input: the authors and books tables above
SELECT a.name, b.title
FROM authors a
FULL OUTER JOIN books b ON a.id = b.author_id;
-- Output:
-- name | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler | Kindred
-- Butler | Parable of the Sower
-- NULL | Beowulf
-- Asimov | NULL
CROSS JOIN
Un CROSS JOIN associe chaque ligne d’une table à chaque ligne de l’autre, il n’a donc pas de ON. Deux tailles et deux couleurs donnent quatre combinaisons ; 1000 lignes par 1000 lignes en donnent un million, alors réserve-le aux petites tables.
-- Input: sizes
-- size
-- ----
-- S
-- M
-- Input: colors
-- color
-- -----
-- Red
-- Blue
SELECT s.size, c.color
FROM sizes s
CROSS JOIN colors c;
-- Output:
-- size | color
-- -----+------
-- S | Red
-- S | Blue
-- M | Red
-- M | Blue
LEFT ANTI JOIN
Un anti-join gauche renvoie les lignes de la table de gauche qui n’ont aucune correspondance dans la table de droite. Il n’existe pas de mot-clé pour ça : fais un LEFT JOIN et garde les lignes où le côté droit vaut NULL.
-- Input: the authors and books tables above
SELECT a.name
FROM authors a
LEFT JOIN books b ON a.id = b.author_id
WHERE b.author_id IS NULL;
-- Output:
-- name
-- ------
-- Asimov
RIGHT ANTI JOIN
Un anti-join droit renvoie les lignes de la table de droite qui n’ont aucune correspondance dans la table de gauche.
-- Input: the authors and books tables above
SELECT b.title
FROM authors a
RIGHT JOIN books b ON a.id = b.author_id
WHERE a.id IS NULL;
-- Output:
-- title
-- -------
-- Beowulf
Règles à connaître
-
NULL ne correspond jamais à rien, pas même à un autre NULL. Le
author_idNULL de Beowulf ne correspond à aucun auteur, donc seules les jointures qui gardent les livres sans correspondance le renvoient. - Une ligne apparaît une fois pour chaque ligne à laquelle elle correspond. Butler apparaît deux fois plus haut car elle correspond à deux livres. Si tu additionnes ensuite une colonne de la table des auteurs, ses valeurs comptent double.
-
Avec une jointure externe, une condition sur l’autre table se place dans
ON. DansWHERE, elle s’exécute après la jointure et élimine les lignes NULL que la jointure venait de garder :
-- Input: the authors and books tables above
-- Only Butler is left: the WHERE drops every author whose title is NULL
SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id
WHERE b.title LIKE 'K%';
-- Output:
-- name | title
-- -------+--------
-- Butler | Kindred
-- Every author is kept, with a book only where it starts with K
SELECT a.name, b.title
FROM authors a
LEFT JOIN books b ON a.id = b.author_id AND b.title LIKE 'K%';
-- Output:
-- name | title
-- --------+--------
-- Le Guin | NULL
-- Butler | Kindred
-- Asimov | NULL
Syntaxe alternative
MySQL n’a pas de FULL OUTER JOIN. Combine plutôt un LEFT JOIN avec les lignes sans correspondance d’un RIGHT JOIN :
-- MySQL
-- Input: the authors and books tables above
SELECT a.name, b.title
FROM authors a LEFT JOIN books b ON a.id = b.author_id
UNION ALL
SELECT a.name, b.title
FROM authors a RIGHT JOIN books b ON a.id = b.author_id
WHERE a.id IS NULL;
-- Output:
-- name | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler | Parable of the Sower
-- Butler | Kindred
-- Asimov | NULL
-- NULL | Beowulf
Avant la version 3.39, SQLite n’a ni RIGHT JOIN ni FULL OUTER JOIN : inverse les tables et utilise un LEFT JOIN.
Un anti-join peut aussi s’écrire avec NOT EXISTS. Évite NOT IN pour ça : si la sous-requête renvoie un seul NULL, comme le author_id de Beowulf ici, NOT IN ne renvoie absolument rien :
-- Input: the authors and books tables above
SELECT a.name
FROM authors a
WHERE NOT EXISTS (SELECT 1 FROM books b WHERE b.author_id = a.id);
-- Output:
-- name
-- ------
-- Asimov
SELECT a.name
FROM authors a
WHERE a.id NOT IN (SELECT author_id FROM books);
-- Output:
-- name
-- ----
-- (0 rows)
Quand les colonnes de correspondance portent le même nom dans les deux tables, tu peux écrire JOIN books USING (author_id) à la place de la condition ON, sauf dans SQL Server.
Le code ancien liste souvent les tables séparées par des virgules et place la correspondance dans WHERE. C’est un INNER JOIN écrit autrement. Sans le WHERE, ça devient un CROSS JOIN :
-- Input: the authors and books tables above
SELECT a.name, b.title
FROM authors a, books b
WHERE a.id = b.author_id;
-- Output:
-- name | title
-- --------+---------------------
-- Le Guin | The Dispossessed
-- Butler | Kindred
-- Butler | Parable of the Sower
Quand utiliser les JOIN SQL ?
Utilise une jointure dès que les données dont tu as besoin sont réparties sur plusieurs tables. Le choix de la jointure dépend du sort que tu réserves aux lignes sans correspondance, et la plupart des cas entrent dans l’une de ces cinq catégories.
Récupérer des détails dans une table liée
Une ligne d’employé contient un ID de service, mais le rapport a besoin du nom du service. Un INNER JOIN va le chercher, et écarte toute ligne qui n’a rien à récupérer.
Garder les lignes sans correspondance
Quand les données liées sont facultatives, par exemple un employé qui n’a pas encore de service, un INNER JOIN le masquerait discrètement. Un LEFT JOIN garde toutes les lignes de la table principale et comble les trous avec NULL.
Lister chaque élément, même les vides
Les rapports ont souvent besoin de chaque service, catégorie ou client, y compris ceux qui ne contiennent encore rien. Fais de cette table celle dont toutes les lignes sont conservées : en premier avec un LEFT JOIN, ou en second avec un RIGHT JOIN.
Trouver les lignes sans correspondance
Clients qui n’ont jamais commandé, produits jamais vendus, employés sans service : un anti-join trouve ce qui manque, ce qui en fait aussi un bon contrôle de qualité des données.
Générer toutes les combinaisons
Un CROSS JOIN génère des lignes qui n’existent pas encore : chaque taille dans chaque couleur, chaque employé face à chaque formation, ou chaque magasin pour chaque jour d’un rapport.
Exemples concrets de JOIN SQL
Les étudiants et leurs cours
Une relation plusieurs-à-plusieurs nécessite deux INNER JOIN, via la table de liaison qui relie les étudiants aux cours. Mei n’est inscrite à rien, elle n’apparaît donc pas :
-- Input: students
-- id | name
-- ---+------
-- 1 | Amara
-- 2 | Lucas
-- 3 | Mei
-- Input: enrollments
-- student_id | course_id | grade
-- -----------+-----------+------
-- 1 | 10 | A
-- 1 | 20 | B
-- 2 | 10 | B
-- Input: courses
-- id | course_name
-- ---+------------
-- 10 | Databases
-- 20 | Statistics
SELECT s.name, c.course_name, e.grade
FROM students s
INNER JOIN enrollments e ON s.id = e.student_id
INNER JOIN courses c ON e.course_id = c.id;
-- Output:
-- name | course_name | grade
-- ------+-------------+------
-- Amara | Databases | A
-- Amara | Statistics | B
-- Lucas | Databases | B
Utilisateurs avec profil facultatif
Tous les utilisateurs n’ont pas rempli leur profil. Un LEFT JOIN les garde tous :
-- Input: users
-- id | username
-- ---+---------
-- 1 | rosa_m
-- 2 | devon
-- 3 | kai_l
-- Input: user_profiles
-- user_id | bio
-- --------+------------------------
-- 1 | Data engineer in Lisbon
-- 3 | Loves SQL puzzles
SELECT u.username, p.bio
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id;
-- Output:
-- username | bio
-- ---------+------------------------
-- rosa_m | Data engineer in Lisbon
-- devon | NULL
-- kai_l | Loves SQL puzzles
Nombre de commandes par client
Pour inclure les clients sans commande, fais un LEFT JOIN depuis customers. Compte ensuite une colonne de la table des commandes : COUNT(*) compte la ligne créée par le LEFT JOIN pour Omar, qui obtiendrait alors 1 au lieu de 0 :
-- Input: customers
-- id | name
-- ---+------------
-- 1 | Harper Lee
-- 2 | Omar Haddad
-- 3 | Yuki Tanaka
-- Input: orders
-- id | customer_id | total
-- ----+-------------+-------
-- 101 | 1 | 59.90
-- 102 | 3 | 120.00
-- 103 | 1 | 14.50
-- Wrong: COUNT(*) counts Omar's NULL row
SELECT c.name, COUNT(*) AS order_count
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
ORDER BY c.id;
-- Output:
-- name | order_count
-- ------------+------------
-- Harper Lee | 2
-- Omar Haddad | 1
-- Yuki Tanaka | 1
-- Right: COUNT(o.id) skips it
SELECT c.name, COUNT(o.id) AS order_count
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name
ORDER BY c.id;
-- Output:
-- name | order_count
-- ------------+------------
-- Harper Lee | 2
-- Omar Haddad | 0
-- Yuki Tanaka | 1
Factures et paiements qui ne concordent pas
Un FULL OUTER JOIN qui ne garde que les lignes sans correspondance compare deux listes en une seule requête : la facture 502 n’a pas été payée, et le paiement 9003 concerne une facture qui n’existe pas :
-- Input: invoices
-- invoice_id | amount
-- -----------+-------
-- 501 | 120.00
-- 502 | 75.50
-- 503 | 300.00
-- Input: payments
-- payment_id | invoice_id | paid
-- -----------+------------+-------
-- 9001 | 501 | 120.00
-- 9002 | 503 | 300.00
-- 9003 | 504 | 42.00
-- MySQL has no FULL OUTER JOIN: use the UNION ALL form under "Alternative syntax"
SELECT i.invoice_id, i.amount, p.payment_id, p.invoice_id AS paid_invoice
FROM invoices i
FULL OUTER JOIN payments p ON p.invoice_id = i.invoice_id
WHERE i.invoice_id IS NULL OR p.payment_id IS NULL;
-- Output:
-- invoice_id | amount | payment_id | paid_invoice
-- -----------+--------+------------+-------------
-- 502 | 75.50 | NULL | NULL
-- NULL | NULL | 9003 | 504
Recettes quotidiennes de chaque magasin, y compris les jours sans vente
Fais un CROSS JOIN entre les magasins et les jours pour obtenir une ligne par magasin et par jour, puis un LEFT JOIN des ventes sur cette grille. York n’a rien vendu le 6 octobre, et a quand même sa ligne :
-- Input: stores
-- store
-- -----
-- Leeds
-- York
-- Input: days
-- day
-- ----------
-- 2026-10-05
-- 2026-10-06
-- Input: sales
-- store | day | amount
-- ------+------------+-------
-- Leeds | 2026-10-05 | 410.00
-- Leeds | 2026-10-06 | 385.50
-- York | 2026-10-05 | 220.00
SELECT st.store, d.day, COALESCE(SUM(s.amount), 0.00) AS takings
FROM stores st
CROSS JOIN days d
LEFT JOIN sales s ON s.store = st.store AND s.day = d.day
GROUP BY st.store, d.day
ORDER BY st.store, d.day;
-- Output:
-- store | day | takings
-- ------+------------+--------
-- Leeds | 2026-10-05 | 410.00
-- Leeds | 2026-10-06 | 385.50
-- York | 2026-10-05 | 220.00
-- York | 2026-10-06 | 0.00
Pour aller plus loin sur les JOIN SQL
Sur ce blog :
- Aide-mémoire des JOIN SQL : chaque type de jointure et son diagramme sur une seule page
- FULL OUTER JOIN sous MySQL : d’autres façons de contourner l’absence de FULL OUTER JOIN dans MySQL
- Guide des jointures MySQL : les jointures dans MySQL, avec exemples
- Guide des jointures SQL Server : les jointures en T-SQL
- CROSS JOIN sous PostgreSQL : d’autres usages des requêtes à toutes les combinaisons
- Guide des relations en base de données : comment les tables que tu joins sont liées entre elles
- Relations un-à-un, guide des relations un-à-plusieurs et guide des relations plusieurs-à-plusieurs : la jointure dont chaque relation a besoin
- Guide de COALESCE en SQL : remplace les NULL laissés par une jointure externe
Documentation officielle :
- PostgreSQL : tables jointes
- MySQL : clause JOIN
- SQLite : la clause FROM et les jointures
- SQL Server : clause FROM et JOIN
Beekeeper Studio est une interface de base de données gratuite & open source
Le meilleur outil de requêtes & éditeur SQL que j’aie jamais utilisé. Il m’offre tout ce dont j’ai besoin pour gérer ma base de données. - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studio est rapide, intuitif et facile à utiliser. Beekeeper prend en charge plein de bases de données et fonctionne parfaitement sous Windows, Mac et Linux.
Ce que les utilisateurs disent de Beekeeper Studio
« Beekeeper Studio a complètement remplacé mon ancien workflow SQL. C’est rapide, intuitif, et ça rend le travail sur les bases de données à nouveau agréable. »
« J’ai essayé beaucoup d’interfaces de bases de données, mais Beekeeper trouve l’équilibre parfait entre fonctionnalités et simplicité. Ça marche, tout simplement. »