⌂ Bases de données
Fiche 10 sur 12

Les jointures

Interroger plusieurs tables à la fois.

Après cette fiche, vous saurez :

1Le problème

SELECT titre, num_auteur FROM livres;
Résultat
titre                     | num_auteur
--------------------------+-----------
Germinal                  |          1
L'Assommoir               |          1
Le Chien jaune            |          2

Un numéro d'auteur n'intéresse personne. On veut le nom. Mais il est dans l'autre table.

C'est le prix de la découpe en tables de la fiche 1 : les données sont bien rangées, mais éparpillées. La jointure les rassemble au moment de la lecture.

2INNER JOIN

SELECT livres.titre, auteurs.nom
FROM livres
INNER JOIN auteurs ON livres.num_auteur = auteurs.num_auteur;
Résultat
titre                     | nom
--------------------------+---------------
Germinal                  | Zola
L'Assommoir               | Zola
Le Chien jaune            | Simenon
Stupeur et tremblements   | Nothomb
Le Petit Prince           | Saint-Exupéry
MorceauRôle
FROM livresLa première table
INNER JOIN auteursLa table qu'on y ajoute
ON …Comment les deux se correspondent

À retenir

La condition ON est presque toujours la même : la clé étrangère d'un côté, la clé primaire de l'autre.

ON livres.num_auteur = auteurs.num_auteur

C'est exactement le lien qu'on a dessiné à la fiche 3, et déclaré à la fiche 7. Le travail d'analyse porte ses fruits ici : si vous savez où sont vos clés étrangères, vous savez écrire vos jointures.

Préfixer les colonnes

Quand deux tables ont une colonne du même nom, il faut préciser laquelle. On écrit table.colonne.

Attention

Sans préfixe, PostgreSQL refuse.

SELECT titre, nom FROM livres INNER JOIN auteurs ON num_auteur = num_auteur;
ERROR: column reference "num_auteur" is ambiguous

Les deux tables ont une colonne num_auteur. PostgreSQL ne peut pas deviner.

3Les alias : raccourcir

SELECT l.titre, a.nom, a.prenom
FROM livres l
INNER JOIN auteurs a ON l.num_auteur = a.num_auteur;

livres l donne le surnom l à la table. On peut aussi écrire livres AS l, c'est identique.

Sur une jointure à trois tables, les alias font gagner énormément en lisibilité. Prenez l'habitude tout de suite.

4LEFT JOIN : garder tout le monde

INNER JOIN ne garde que les lignes qui ont une correspondance des deux côtés. Parfois, ce n'est pas ce qu'on veut.

Question : quels sont tous les auteurs, avec leurs livres ?

INSERT INTO auteurs (nom, prenom, nationalite)
VALUES ('Hugo', 'Victor', 'Française');

SELECT a.nom, l.titre
FROM auteurs a
INNER JOIN livres l ON a.num_auteur = l.num_auteur;

Victor Hugo n'apparaît pas. Il n'a aucun livre dans la base, donc aucune correspondance, donc INNER JOIN l'écarte.

SELECT a.nom, l.titre
FROM auteurs a
LEFT JOIN livres l ON a.num_auteur = l.num_auteur;
Résultat
nom            | titre
---------------+--------------------------
Zola           | Germinal
Zola           | L'Assommoir
Simenon        | Le Chien jaune
Nothomb        | Stupeur et tremblements
Saint-Exupéry  | Le Petit Prince
Hugo           | NULL

Avec LEFT JOIN, Hugo apparaît, avec NULL dans la colonne titre. C'est bien plus honnête : il existe dans la base.

JointureGarde…
INNER JOINseulement les lignes qui ont une correspondance des deux côtés
LEFT JOINtoutes les lignes de la table de gauche, complétées par NULL
RIGHT JOINtoutes les lignes de la table de droite
FULL JOINtoutes les lignes des deux tables

À retenir

Comment choisir ? Posez-vous une seule question : est-ce que je veux voir les éléments qui n'ont rien ?

RIGHT JOIN fait la même chose que LEFT JOIN en inversant l'ordre des tables. En pratique, on n'en écrit presque jamais : on préfère mettre la table importante à gauche.

Trouver ce qui n'a rien

Une astuce très utile : combiner LEFT JOIN et IS NULL.

-- Les auteurs sans aucun livre
SELECT a.nom, a.prenom
FROM auteurs a
LEFT JOIN livres l ON a.num_auteur = l.num_auteur
WHERE l.num_livre IS NULL;
Résultat
nom   | prenom
------+--------
Hugo  | Victor

On garde tout le monde, puis on ne conserve que ceux dont le côté droit est vide.

5Joindre trois tables

Question : qui a emprunté quoi ? La réponse traverse trois tables, comme prévu par le n-n de la fiche 3.

SELECT lec.nom AS lecteur,
       liv.titre AS livre,
       e.date_emprunt
FROM emprunts e
INNER JOIN lecteurs lec ON e.num_lecteur = lec.num_lecteur
INNER JOIN livres liv ON e.num_livre = liv.num_livre
ORDER BY e.date_emprunt;
Résultat
lecteur | livre            | date_emprunt
--------+------------------+-------------
Benali  | Le Petit Prince  | 2026-08-15
Dupuis  | Germinal         | 2026-09-01
Dupuis  | Le Chien jaune   | 2026-09-05
Martin  | L'Assommoir      | 2026-09-10

À retenir

La méthode, quel que soit le nombre de tables :

  1. Partez de la table du milieu — celle qui porte les clés étrangères.
  2. Ajoutez un INNER JOIN par table à rattacher.
  3. Chaque ON relie une clé étrangère à sa clé primaire.

Ici emprunts est au milieu : elle pointe vers lecteurs et vers livres. C'est toujours la table de liaison qui sert de point de départ.

On peut évidemment ajouter un filtre et remonter jusqu'à l'auteur :

SELECT lec.nom AS lecteur,
       liv.titre AS livre,
       aut.nom AS auteur,
       e.date_prevue
FROM emprunts e
INNER JOIN lecteurs lec ON e.num_lecteur = lec.num_lecteur
INNER JOIN livres liv ON e.num_livre = liv.num_livre
INNER JOIN auteurs aut ON liv.num_auteur = aut.num_auteur
WHERE e.date_retour IS NULL
ORDER BY e.date_prevue;

Quatre tables, et la requête reste lisible grâce aux alias.

→Exercices

1Voir la différenceRecopier

Ajoutez d'abord un auteur sans livre et un lecteur sans emprunt.

INSERT INTO auteurs (nom, prenom, nationalite)
VALUES ('Hugo', 'Victor', 'Française');

INSERT INTO lecteurs (nom, prenom, telephone)
VALUES ('Petit', 'Lucie', '0470 99 88 77');

Puis exécutez ces deux requêtes et comparez le nombre de lignes.

-- A
SELECT a.nom, l.titre
FROM auteurs a
INNER JOIN livres l ON a.num_auteur = l.num_auteur;

-- B
SELECT a.nom, l.titre
FROM auteurs a
LEFT JOIN livres l ON a.num_auteur = l.num_auteur;

Solution

A : 5 lignes. Les cinq livres, chacun avec son auteur. Hugo est absent.

B : 6 lignes. Les cinq mêmes, plus Hugo avec NULL.

Pourquoi Zola apparaît-il deux fois ? Parce qu'il a deux livres. Une jointure produit une ligne par correspondance, pas une ligne par auteur.

C'est normal, et c'est important à comprendre : le résultat d'une jointure n'a pas le même nombre de lignes que la table de départ. Nous verrons à la fiche 11 comment regrouper pour compter.

Et si vous vouliez « tous les lecteurs, même sans emprunt » ?

SELECT lec.nom, e.date_emprunt
FROM lecteurs lec
LEFT JOIN emprunts e ON lec.num_lecteur = e.num_lecteur;

Lucie Petit apparaîtra avec NULL.

2Écrire des jointuresModifier

Écrivez la requête qui répond à chaque question.

  1. Le titre de chaque livre avec le nom et le prénom de son auteur.
  2. Les livres belges (dont l'auteur est belge).
  3. Tous les lecteurs, avec le nombre de jours depuis leur dernier emprunt — y compris ceux qui n'ont jamais emprunté.
  4. Les emprunts en retard, avec le nom du lecteur, le titre du livre et le nombre de jours de retard.
  5. Les livres qui n'ont jamais été empruntés.

Solution

-- 1
SELECT l.titre, a.nom, a.prenom
FROM livres l
INNER JOIN auteurs a ON l.num_auteur = a.num_auteur
ORDER BY a.nom;

-- 2
SELECT l.titre, a.nom
FROM livres l
INNER JOIN auteurs a ON l.num_auteur = a.num_auteur
WHERE a.nationalite = 'Belge';

-- 3
SELECT lec.nom,
       lec.prenom,
       e.date_emprunt,
       CURRENT_DATE - e.date_emprunt AS jours
FROM lecteurs lec
LEFT JOIN emprunts e ON lec.num_lecteur = e.num_lecteur
ORDER BY lec.nom;

-- 4
SELECT lec.nom AS lecteur,
       liv.titre AS livre,
       e.date_prevue,
       CURRENT_DATE - e.date_prevue AS jours_de_retard
FROM emprunts e
INNER JOIN lecteurs lec ON e.num_lecteur = lec.num_lecteur
INNER JOIN livres liv ON e.num_livre = liv.num_livre
WHERE e.date_retour IS NULL
  AND e.date_prevue < CURRENT_DATE
ORDER BY jours_de_retard DESC;

-- 5
SELECT liv.titre
FROM livres liv
LEFT JOIN emprunts e ON liv.num_livre = e.num_livre
WHERE e.num_emprunt IS NULL;

Sur la 3 : LEFT JOIN est indispensable. Avec INNER JOIN, Lucie Petit disparaîtrait, alors que l'énoncé demande explicitement « y compris ceux qui n'ont jamais emprunté ».

Sur la 4 : c'est la requête la plus utile de la fiche. Elle deviendra l'état de sortie de la fiche 12. Remarquez qu'on peut trier sur jours_de_retard, l'alias défini plus haut — PostgreSQL l'accepte dans le ORDER BY.

Sur la 5 : c'est le motif « LEFT JOIN + IS NULL » de la section 4. Retenez-le, il répond à toute question de la forme « qui n'a pas de… ».

3Les jointures de votre baseCréer

Sur votre propre base.

  1. Écrivez une jointure entre deux tables.
  2. Écrivez une jointure entre trois tables.
  3. Écrivez une requête qui trouve les éléments « sans rien » (LEFT JOIN + IS NULL).
  4. Pour chacune, écrivez la question en français en commentaire.

Solution

-- Quelles consultations, avec le nom du patient ?
SELECT c.date_consult, c.motif, p.nom, p.prenom
FROM consultations c
INNER JOIN patients p ON c.num_patient = p.num_patient
ORDER BY c.date_consult DESC;

-- Qui a consulté qui, et pour quel montant ?
SELECT c.date_consult,
       p.nom AS patient,
       m.nom AS medecin,
       m.specialite,
       c.montant
FROM consultations c
INNER JOIN patients p ON c.num_patient = p.num_patient
INNER JOIN medecins m ON c.num_medecin = m.num_medecin
ORDER BY c.date_consult DESC;

-- Quels patients ne sont jamais venus en consultation ?
SELECT p.nom, p.prenom, p.telephone
FROM patients p
LEFT JOIN consultations c ON p.num_patient = c.num_patient
WHERE c.num_consultation IS NULL;

Les trois points à vérifier dans votre travail :

  • Chaque ON relie bien une clé étrangère à une clé primaire. Si vous joignez sur autre chose, c'est probablement une erreur.
  • Vous avez utilisé des alias de table. Sur trois tables, c'est indispensable.
  • Pour la troisième requête, vous testez IS NULL sur une colonne de la table de droite — idéalement sa clé primaire, qui n'est jamais vide autrement.

Une erreur fréquente dans la requête 3 : tester WHERE c.motif IS NULL. Ça ne marche pas, parce qu'une consultation peut exister avec un motif vide. Il faut tester une colonne qui ne peut être NULL que par absence de correspondance : la clé primaire.

Je sais faire

Une case qui coince ? Relisez la partie concernée avant de passer à la fiche suivante.

← Fiche 9 Fiche 11 : Regrouper et compter →