🔗 Les Jointures SQL (JOIN)
Maîtrisez l'art de combiner des données provenant de plusieurs tables
Pourquoi les jointures ?
Le problème
Dans une base de données relationnelle, les informations sont souvent réparties dans plusieurs tables pour éviter la redondance et améliorer l'organisation. Les jointures (JOIN) permettent de récupérer ces données en combinant plusieurs tables dans une seule requête.
Exemple concret
Imaginez une bibliothèque avec deux tables :
Table : auteur
| id | nom | pays |
|---|---|---|
| 1 | Victor Hugo | France |
| 2 | J.K. Rowling | Royaume-Uni |
| 3 | Haruki Murakami | Japon |
Table : livre
| id | titre | auteur_id | annee |
|---|---|---|---|
| 1 | Les Misérables | 1 | 1862 |
| 2 | Harry Potter | 2 | 1997 |
| 3 | 1Q84 | 3 | 2009 |
Question : Comment récupérer la liste des livres avec le nom de l'auteur en une seule requête ?
Réponse : Avec une jointure ! 🎯
Clés primaires et clés étrangères
Vocabulaire clé
- Clé primaire (PK) : Identifiant unique d'une ligne dans une table
- Clé étrangère (FK) : Référence à la clé primaire d'une autre table
- Relation : Lien logique entre deux tables via une clé étrangère
Types de relations entre tables
Un à Un (1:1)
Une ligne de la table A correspond à une seule ligne de la table B.
utilisateur ⟷ profilUn utilisateur a un seul profil
utilisateur [1] ←→ [1] profil
Un à Plusieurs (1:N)
Une ligne de la table A peut correspondre à plusieurs lignes de la table B.
auteur ⟷ livreUn auteur écrit plusieurs livres
auteur [1] ←→ [N] livres
Plusieurs à Plusieurs (N:M)
Plusieurs lignes de A correspondent à plusieurs lignes de B. Nécessite une table de jonction.
etudiant ⟷ coursUn étudiant suit plusieurs cours, un cours a plusieurs étudiants
etudiant [N] ←→ [M] cours via table: inscription
Table de jonction (pour relations N:M)
etudiant
| id | nom |
|---|---|
| 1 | Alice |
| 2 | Bob |
inscription
| etudiant_id | cours_id |
|---|---|
| 1 | 101 |
| 1 | 102 |
| 2 | 101 |
cours
| id | nom |
|---|---|
| 101 | Math |
| 102 | Anglais |
INNER JOIN - Jointure interne
Retourne uniquement les lignes qui ont une correspondance dans les deux tables.
Syntaxe
SELECT table1.colonne1, table2.colonne2
FROM table1
INNER JOIN table2 ON table1.cle = table2.cle;
Exemple pratique
SELECT
livre.titre,
livre.annee,
auteur.nom AS nom_auteur,
auteur.pays
FROM livre
INNER JOIN auteur ON livre.auteur_id = auteur.id;
📊 Résultat
| titre | annee | nom_auteur | pays |
|---|---|---|---|
| Les Misérables | 1862 | Victor Hugo | France |
| Harry Potter | 1997 | J.K. Rowling | Royaume-Uni |
| 1Q84 | 2009 | Haruki Murakami | Japon |
Important
Si un livre n'a pas d'auteur assigné (auteur_id = NULL), il ne sera pas inclus dans le résultat d'un INNER JOIN.
Alias de tables
Pour simplifier la lecture, utilisez des alias :
SELECT
l.titre,
a.nom AS auteur
FROM livre AS l
INNER JOIN auteur AS a ON l.auteur_id = a.id;
LEFT JOIN - Jointure gauche
Retourne toutes les lignes de la table de gauche, même si elles n'ont pas de correspondance à droite.
Syntaxe
SELECT table1.colonne1, table2.colonne2
FROM table1
LEFT JOIN table2 ON table1.cle = table2.cle;
Exemple
Supposons qu'un livre n'a pas d'auteur assigné :
Table : livre
| id | titre | auteur_id |
|---|---|---|
| 1 | Les Misérables | 1 |
| 2 | Livre Orphelin | NULL |
SELECT
l.titre,
a.nom AS auteur
FROM livre AS l
LEFT JOIN auteur AS a ON l.auteur_id = a.id;
📊 Résultat
| titre | auteur |
|---|---|
| Les Misérables | Victor Hugo |
| Livre Orphelin | NULL |
⚠️ Le livre sans auteur apparaît quand même !
💡 Quand utiliser LEFT JOIN ?
- Trouver les enregistrements sans correspondance
- Afficher toutes les données de la table principale
- Compter les relations (ex: livres par auteur, y compris 0)
Trouver les orphelins
SELECT l.titre
FROM livre AS l
LEFT JOIN auteur AS a ON l.auteur_id = a.id
WHERE a.id IS NULL;
RIGHT JOIN - Jointure droite
Retourne toutes les lignes de la table de droite, même si elles n'ont pas de correspondance à gauche.
SQLite et RIGHT JOIN
SQLite ne supporte pas RIGHT JOIN, mais vous pouvez obtenir le même résultat en inversant les tables avec LEFT JOIN :
RIGHT JOIN ≈ LEFT JOIN inversé
-- RIGHT JOIN (non supporté dans SQLite)
SELECT a.nom, l.titre
FROM livre AS l
RIGHT JOIN auteur AS a ON l.auteur_id = a.id;
-- Équivalent avec LEFT JOIN (fonctionne dans SQLite)
SELECT a.nom, l.titre
FROM auteur AS a
LEFT JOIN livre AS l ON l.auteur_id = a.id;
Exemple : Auteurs sans livres
SELECT
a.nom AS auteur,
COUNT(l.id) AS nombre_livres
FROM auteur AS a
LEFT JOIN livre AS l ON l.auteur_id = a.id
GROUP BY a.id, a.nom;
FULL OUTER JOIN - Jointure complète
Retourne toutes les lignes des deux tables, avec ou sans correspondance.
SQLite et FULL OUTER JOIN
SQLite ne supporte pas FULL OUTER JOIN, mais vous pouvez le simuler avec UNION :
-- FULL OUTER JOIN simulé dans SQLite
SELECT l.titre, a.nom
FROM livre AS l
LEFT JOIN auteur AS a ON l.auteur_id = a.id
UNION
SELECT l.titre, a.nom
FROM auteur AS a
LEFT JOIN livre AS l ON l.auteur_id = a.id;
📊 Résultat
Cette requête retourne :
- Tous les livres avec leurs auteurs
- Les livres sans auteur (NULL)
- Les auteurs sans livres (NULL)
CROSS JOIN - Produit cartésien
Combine chaque ligne de la table A avec chaque ligne de la table B. Produit cartésien.
Attention !
Si la table A a 10 lignes et la table B a 20 lignes, le résultat aura 10 × 20 = 200 lignes !
SELECT *
FROM table1
CROSS JOIN table2;
-- Équivalent à :
SELECT *
FROM table1, table2;
Exemple : Combiner des couleurs et des tailles
couleurs
| couleur |
|---|
| Rouge |
| Bleu |
tailles
| taille |
|---|
| S |
| M |
SELECT c.couleur, t.taille
FROM couleurs AS c
CROSS JOIN tailles AS t;
📊 Résultat (2 × 2 = 4 lignes)
| couleur | taille |
|---|---|
| Rouge | S |
| Rouge | M |
| Bleu | S |
| Bleu | M |
💡 Cas d'utilisation
- Générer toutes les combinaisons possibles
- Créer des variations de produits (e-commerce)
- Tests combinatoires
Jointures multiples
Vous pouvez combiner plusieurs JOIN dans une seule requête pour relier 3 tables ou plus.
Exemple : Bibliothèque complète
auteur
| id | nom |
|---|---|
| 1 | Hugo |
livres
| id | titre | auteur_id | categorie_id |
|---|---|---|---|
| 1 | Les Mis | 1 | 10 |
categorie
| id | nom |
|---|---|
| 10 | Roman |
SELECT
l.titre,
a.nom AS auteur,
c.nom AS categorie
FROM livre AS l
INNER JOIN auteur AS a ON l.auteur_id = a.id
INNER JOIN categorie AS c ON l.categorie_id = c.id;
Relations N:M avec table de jonction
Pour une relation plusieurs-à-plusieurs, vous devez joindre 3 tables :
SELECT
e.nom AS etudiant,
c.nom AS cours,
i.note
FROM etudiant AS e
INNER JOIN inscription AS i ON e.id = i.etudiant_id
INNER JOIN cours AS c ON i.cours_id = c.id
ORDER BY e.nom, c.nom;
📊 Résultat
| etudiant | cours | note |
|---|---|---|
| Alice | Mathématiques | 85 |
| Alice | Anglais | 92 |
| Bob | Mathématiques | 78 |
Mélanger différents types de JOIN
SELECT
l.titre,
a.nom AS auteur,
c.nom AS categorie
FROM livre AS l
INNER JOIN auteur AS a ON l.auteur_id = a.id
LEFT JOIN categorie AS c ON l.categorie_id = c.id;
-- Tous les livres avec auteur, même sans catégorie
Optimisation des jointures
1. Utilisez des index
Créez des index sur les colonnes de jointure (clés étrangères) pour accélérer les requêtes.
CREATE INDEX idx_livre_auteur
ON livre(auteur_id);
2. Sélectionnez uniquement ce dont vous avez besoin
Évitez SELECT *, spécifiez les colonnes exactes.
-- ❌ Mauvais
SELECT * FROM livre JOIN auteur...
-- ✅ Bon
SELECT l.titre, a.nom FROM livre...
3. Utilisez LIMIT
Limitez le nombre de résultats si vous n'avez pas besoin de tout.
SELECT l.titre, a.nom
FROM livre l
JOIN auteur a ON l.auteur_id = a.id
LIMIT 10;
4. Analysez vos requêtes
Utilisez EXPLAIN QUERY PLAN pour voir comment SQLite exécute votre requête.
EXPLAIN QUERY PLAN
SELECT l.titre, a.nom
FROM livre l
JOIN auteur a ON l.auteur_id = a.id;
Bonnes pratiques
- Préférez INNER JOIN à LEFT JOIN quand c'est possible (plus rapide)
- Filtrez avec WHERE avant de joindre quand possible
- Utilisez des alias courts et lisibles
- Testez vos requêtes avec de vraies données volumineuses
Comparer les performances
.timer ON
SELECT l.titre, a.nom
FROM livre l
INNER JOIN auteur a ON l.auteur_id = a.id;
-- Affiche : Run Time: real 0.001 user 0.000000 sys 0.000000
Jointures dans une API PHP
Voyons comment utiliser les jointures SQL dans une API REST avec PHP et PDO.
Exemple : API de bibliothèque
<?php
header('Content-Type: application/json; charset=utf-8');
// Connexion SQLite
try {
$pdo = new PDO('sqlite:bibliotheque.db');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['error' => 'Erreur de connexion']);
exit;
}
// Récupérer tous les livres avec leurs auteurs et catégories
$sql = "
SELECT
l.id,
l.titre,
l.annee,
l.prix,
a.nom AS auteur_nom,
a.pays AS auteur_pays,
c.nom AS categorie
FROM livre AS l
INNER JOIN auteur AS a ON l.auteur_id = a.id
LEFT JOIN categorie AS c ON l.categorie_id = c.id
ORDER BY l.titre
";
$stmt = $pdo->query($sql);
$livres = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Retourner JSON
echo json_encode([
'success' => true,
'data' => $livres,
'count' => count($livres)
], JSON_PRETTY_PRINT);
?>
📊 Réponse JSON
{
"success": true,
"data": [
{
"id": 1,
"titre": "Les Misérables",
"annee": 1862,
"prix": 15.99,
"auteur_nom": "Victor Hugo",
"auteur_pays": "France",
"categorie": "Roman"
},
{
"id": 2,
"titre": "Harry Potter",
"annee": 1997,
"prix": 12.99,
"auteur_nom": "J.K. Rowling",
"auteur_pays": "Royaume-Uni",
"categorie": "Fantasy"
}
],
"count": 2
}
Endpoint avec paramètres
<?php
// GET /api/auteur/{id}/livre
$auteurId = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$sql = "
SELECT
l.id,
l.titre,
l.annee,
a.nom AS auteur
FROM livre AS l
INNER JOIN auteur AS a ON l.auteur_id = a.id
WHERE a.id = :auteur_id
ORDER BY l.annee DESC
";
$stmt = $pdo->prepare($sql);
$stmt->execute([':auteur_id' => $auteurId]);
$livres = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($livres)) {
http_response_code(404);
echo json_encode(['error' => 'Aucun livre trouvé pour cet auteur']);
exit;
}
echo json_encode([
'success' => true,
'data' => $livres
], JSON_PRETTY_PRINT);
?>
Statistiques avec GROUP BY
<?php
$sql = "
SELECT
a.id,
a.nom,
a.pays,
COUNT(l.id) AS nombre_livres
FROM auteur AS a
LEFT JOIN livre AS l ON l.auteur_id = a.id
GROUP BY a.id, a.nom, a.pays
ORDER BY nombre_livres DESC
";
$stmt = $pdo->query($sql);
$stats = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($stats, JSON_PRETTY_PRINT);
?>
Conseil
Dans une API REST, structurez vos réponses pour inclure les relations imbriquées quand c'est logique. Par exemple, un auteur avec sa liste de livres.
Exercices pratiques
Première jointure simple
Base de données : Créez deux tables (produit et categorie)
CREATE TABLE categorie (
id INTEGER PRIMARY KEY,
nom TEXT NOT NULL
);
CREATE TABLE produit (
id INTEGER PRIMARY KEY,
nom TEXT NOT NULL,
prix REAL,
categorie_id INTEGER,
FOREIGN KEY (categorie_id) REFERENCES categorie(id)
);
Tâche : Écrivez une requête INNER JOIN pour afficher tous les produits avec leur catégorie.
INNER JOIN categorie ON ...
Compter avec GROUP BY
Tâche : Comptez le nombre de produits par catégorie.
COUNT(p.id) et GROUP BY c.id
Produits orphelins
Tâche : Trouvez tous les produits qui n'ont pas de catégorie assignée.
WHERE categorie_id IS NULL
Relation N:M - Commandes
Contexte : Créez un système de commandes :
- Table
clients: id, nom - Table
produit: id, nom, prix - Table
commandes: id, client_id, date - Table
commande_produit: commande_id, produit_id, quantite
Tâche : Écrivez une requête qui affiche toutes les commandes avec :
- Le nom du client
- Les produits commandés
- Les quantités
- Le total de chaque commande
API avec jointures
Tâche : Créez une API REST en PHP avec les endpoints suivants :
GET /api/categorie: Liste des catégories avec le nombre de produitsGET /api/categorie/{id}/produit: Produits d'une catégorieGET /api/produit: Produits avec leur catégorie (pagination)GET /api/stat: Statistiques (total produits, moyenne prix par catégorie, etc.)
Système de blog complet
Objectif : Créez une base de données et une API pour un blog
Structure :
utilisateur: id, nom, emailarticle: id, titre, contenu, auteur_id, categorie_id, datecategorie: id, nomcommentaire: id, article_id, auteur_id, contenu, datetag: id, nomarticle_tag: article_id, tag_id
Requêtes à implémenter :
- Articles avec auteurs, catégories et tags
- Commentaires d'un article avec infos auteur
- Articles par tag
- Top 10 auteurs (plus d'articles)
- Statistiques complètes
Glossaire
JOIN (Jointure)
Opération SQL qui combine les lignes de deux ou plusieurs tables basées sur une condition de relation.
Clé primaire (Primary Key - PK)
Colonne(s) qui identifie de manière unique chaque ligne d'une table. Ne peut pas être NULL et doit être unique.
Clé étrangère (Foreign Key - FK)
Colonne qui établit un lien entre deux tables en référençant la clé primaire d'une autre table.
INNER JOIN
Retourne uniquement les lignes qui ont une correspondance dans les deux tables.
LEFT JOIN (LEFT OUTER JOIN)
Retourne toutes les lignes de la table de gauche, avec les correspondances de la table de droite (NULL si aucune correspondance).
RIGHT JOIN (RIGHT OUTER JOIN)
Retourne toutes les lignes de la table de droite, avec les correspondances de la table de gauche. Non supporté dans SQLite.
FULL OUTER JOIN
Retourne toutes les lignes des deux tables, avec ou sans correspondance. Non supporté dans SQLite (utilisez UNION).
CROSS JOIN
Produit cartésien : chaque ligne de la table A est combinée avec chaque ligne de la table B.
Table de jonction
Table intermédiaire utilisée pour implémenter une relation plusieurs-à-plusieurs (N:M).
Relation 1:1 (Un à Un)
Une ligne de la table A correspond à exactement une ligne de la table B.
Relation 1:N (Un à Plusieurs)
Une ligne de la table A peut correspondre à plusieurs lignes de la table B.
Relation N:M (Plusieurs à Plusieurs)
Plusieurs lignes de A correspondent à plusieurs lignes de B. Nécessite une table de jonction.
Alias de table
Nom court donné à une table pour simplifier les requêtes (ex: FROM livre AS l).
Index
Structure de données qui accélère la recherche et les jointures sur une colonne.
Ressources supplémentaires
SQLite SELECT
Documentation officielle de SQLite sur les SELECT et JOIN
SQL.sh - Jointures
Tutoriel français complet sur les jointures SQL
W3Schools SQL JOIN
Tutoriels interactifs avec exemples
DB Browser for SQLite
Outil visuel gratuit pour gérer vos bases SQLite
SQLite Tutorial
Cours complet SQLite en anglais
Visual JOIN
Visualiseur interactif de jointures SQL