Relier deux tables avec INNER JOIN

Première leçon du cours SQL intermédiaire (jointures, sous-requêtes), en accès libre : environ 18 minutes, 3 exercices et 3 questions de quiz. C'est la leçon de base du catalogue ; dans l'espace étudiant, elle est réécrite pour ton profil.

Objectifs

  • Comprendre pourquoi les données sont réparties dans plusieurs tables reliées par des clés
  • Écrire une jointure interne avec JOIN ... ON
  • Utiliser des alias de tables et qualifier les colonnes ambiguës
  • Agréger le résultat d'une jointure

Clé primaire et clé étrangèreINNER JOINAlias de tables

Dans une vraie base, les informations ne sont pas toutes dans une seule table. Une boutique stocke ses clients dans une table et ses commandes dans une autre. Pourquoi ? Pour ne pas recopier le nom et la ville d’un client dans chacune de ses commandes : si le client déménage, une seule ligne est à modifier.

Le lien entre les deux tables se fait par des clés :

  • clients.id est la clé primaire : elle identifie chaque client de façon unique ;
  • commandes.client_id est une clé étrangère : elle contient l’id du client qui a passé la commande.
clients                  commandes
id | nom     | ville     id  | client_id | statut
1  | Amina   | Lyon      101 | 1         | payee
2  | Bastien | Paris     103 | 2         | annulee

La jointure interne

Pour afficher chaque commande avec le nom du client, on joint les deux tables :

SELECT commandes.id, commandes.date_commande, clients.nom
FROM commandes
INNER JOIN clients
        ON clients.id = commandes.client_id;   -- condition de correspondance

Pour chaque commande, la base cherche le ou les clients dont l’id est égal à client_id, et fabrique une ligne combinée. INNER est facultatif : JOIN seul signifie jointure interne.

Une jointure interne ne garde que les lignes qui ont une correspondance des deux côtés. Un client sans commande (Emma) n’apparaît donc pas dans le résultat. La leçon suivante montre comment le conserver.

Les alias de tables

Écrire le nom complet des tables devient vite lourd. On leur donne un alias court :

SELECT co.id, co.date_commande, cl.nom, cl.ville
FROM commandes AS co
JOIN clients   AS cl ON cl.id = co.client_id
WHERE co.statut = 'payee'
ORDER BY co.date_commande;

Les deux tables possèdent une colonne id. Écrire id seul serait ambigu et provoquerait une erreur : on précise donc co.id ou cl.id. Une bonne habitude consiste à préfixer toutes les colonnes dès qu’il y a une jointure.

Les jointures multiplient les lignes

Une jointure ne renvoie pas « une ligne par table de départ » mais une ligne par correspondance. Amina a deux commandes : elle apparaît deux fois. C’est normal, et c’est ce qui permet ensuite de compter ou d’additionner.

Joindre puis agréger

Le résultat d’une jointure se manipule comme une table : on peut le filtrer, le regrouper et le trier.

-- Nombre de commandes par ville de client
SELECT cl.ville, COUNT(*) AS nb_commandes
FROM commandes AS co
JOIN clients   AS cl ON cl.id = co.client_id
GROUP BY cl.ville
ORDER BY nb_commandes DESC;

Méthode

  1. Identifier les tables qui contiennent les informations voulues.
  2. Repérer la clé étrangère qui les relie.
  3. Écrire le JOIN ... ON et vérifier le nombre de lignes obtenu.
  4. Ajouter ensuite filtres, regroupements et tri.

Prendre l’habitude de vérifier le nombre de lignes après chaque jointure évite la plupart des erreurs de calcul.

Exercices

Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.

  1. CodeDifficulté 1SQL (SQLite) dans le navigateur2 indices

    Commandes et nom du client

    Tables disponibles : clients(id, nom, ville), commandes(id, client_id, date_commande, statut), produits(id, nom, categorie, prix), lignes_commande(commande_id, produit_id, quantite), fournisseurs(id, nom, ville).

    Affichez l’id de chaque commande et le nom du client qui l’a passée, triés par id de commande.

    Données de l'exercice (SQL)
    CREATE TABLE clients (
      id INTEGER PRIMARY KEY,
      nom TEXT NOT NULL,
      ville TEXT NOT NULL
    );
    CREATE TABLE commandes (
      id INTEGER PRIMARY KEY,
      client_id INTEGER NOT NULL REFERENCES clients(id),
      date_commande TEXT NOT NULL,
      statut TEXT NOT NULL            -- 'payee' ou 'annulee'
    );
    CREATE TABLE produits (
      id INTEGER PRIMARY KEY,
      nom TEXT NOT NULL,
      categorie TEXT NOT NULL,
      prix INTEGER NOT NULL
    );
    CREATE TABLE lignes_commande (
      commande_id INTEGER NOT NULL REFERENCES commandes(id),
      produit_id INTEGER NOT NULL REFERENCES produits(id),
      quantite INTEGER NOT NULL,
      PRIMARY KEY (commande_id, produit_id)
    );
    CREATE TABLE fournisseurs (
      id INTEGER PRIMARY KEY,
      nom TEXT NOT NULL,
      ville TEXT NOT NULL
    );
    INSERT INTO clients VALUES
     (1, 'Amina', 'Lyon'), (2, 'Bastien', 'Paris'), (3, 'Camille', 'Lille'),
     (4, 'David', 'Paris'), (5, 'Emma', 'Nantes');
    INSERT INTO commandes VALUES
     (101, 1, '2026-01-10', 'payee'), (102, 1, '2026-02-03', 'payee'),
     (103, 2, '2026-02-15', 'annulee'), (104, 3, '2026-03-01', 'payee'),
     (105, 4, '2026-03-05', 'payee'), (106, 2, '2026-03-20', 'payee');
    INSERT INTO produits VALUES
     (1, 'Clavier', 'Informatique', 50), (2, 'Souris', 'Informatique', 20),
     (3, 'Lampe', 'Maison', 35), (4, 'Chaise', 'Mobilier', 189), (5, 'Plante', 'Maison', 15);
    INSERT INTO lignes_commande VALUES
     (101, 1, 1), (101, 2, 2), (102, 3, 1), (103, 4, 1),
     (104, 2, 3), (104, 3, 2), (105, 4, 2), (106, 1, 1), (106, 2, 1);
    INSERT INTO fournisseurs VALUES
     (1, 'Atelier Bois', 'Nantes'), (2, 'TechPro', 'Lyon'), (3, 'Verdure', 'Bordeaux');
    SELECT co.id, cl.nom
    FROM commandes AS co
    JOIN ...
    ORDER BY co.id;
  2. CodeDifficulté 2SQL (SQLite) dans le navigateur2 indices

    Commandes par ville

    Tables disponibles : clients(id, nom, ville), commandes(id, client_id, date_commande, statut), produits(id, nom, categorie, prix), lignes_commande(commande_id, produit_id, quantite), fournisseurs(id, nom, ville).

    Affichez chaque ville de client et le nombre de commandes (tous statuts, alias nb_commandes) passées par les clients de cette ville. Triez par nombre décroissant.

    Données de l'exercice (SQL)
    CREATE TABLE clients (
      id INTEGER PRIMARY KEY,
      nom TEXT NOT NULL,
      ville TEXT NOT NULL
    );
    CREATE TABLE commandes (
      id INTEGER PRIMARY KEY,
      client_id INTEGER NOT NULL REFERENCES clients(id),
      date_commande TEXT NOT NULL,
      statut TEXT NOT NULL            -- 'payee' ou 'annulee'
    );
    CREATE TABLE produits (
      id INTEGER PRIMARY KEY,
      nom TEXT NOT NULL,
      categorie TEXT NOT NULL,
      prix INTEGER NOT NULL
    );
    CREATE TABLE lignes_commande (
      commande_id INTEGER NOT NULL REFERENCES commandes(id),
      produit_id INTEGER NOT NULL REFERENCES produits(id),
      quantite INTEGER NOT NULL,
      PRIMARY KEY (commande_id, produit_id)
    );
    CREATE TABLE fournisseurs (
      id INTEGER PRIMARY KEY,
      nom TEXT NOT NULL,
      ville TEXT NOT NULL
    );
    INSERT INTO clients VALUES
     (1, 'Amina', 'Lyon'), (2, 'Bastien', 'Paris'), (3, 'Camille', 'Lille'),
     (4, 'David', 'Paris'), (5, 'Emma', 'Nantes');
    INSERT INTO commandes VALUES
     (101, 1, '2026-01-10', 'payee'), (102, 1, '2026-02-03', 'payee'),
     (103, 2, '2026-02-15', 'annulee'), (104, 3, '2026-03-01', 'payee'),
     (105, 4, '2026-03-05', 'payee'), (106, 2, '2026-03-20', 'payee');
    INSERT INTO produits VALUES
     (1, 'Clavier', 'Informatique', 50), (2, 'Souris', 'Informatique', 20),
     (3, 'Lampe', 'Maison', 35), (4, 'Chaise', 'Mobilier', 189), (5, 'Plante', 'Maison', 15);
    INSERT INTO lignes_commande VALUES
     (101, 1, 1), (101, 2, 2), (102, 3, 1), (103, 4, 1),
     (104, 2, 3), (104, 3, 2), (105, 4, 2), (106, 1, 1), (106, 2, 1);
    INSERT INTO fournisseurs VALUES
     (1, 'Atelier Bois', 'Nantes'), (2, 'TechPro', 'Lyon'), (3, 'Verdure', 'Bordeaux');
    SELECT cl.ville, COUNT(*) AS nb_commandes
    FROM ...
    GROUP BY ...
    ORDER BY ...;
  3. QCMDifficulté 11 indice

    Colonne ambiguë

    La requête SELECT id, nom FROM commandes JOIN clients ON clients.id = commandes.client_id; échoue. Pourquoi ?

    • La condition ON est inversée
    • La colonne id existe dans les deux tables et n'est pas qualifiée
    • JOIN doit toujours s'écrire INNER JOIN

Quiz de fin de leçon

  1. Que contient commandes.client_id ?

    • Le nom du client
    • L'identifiant (clé primaire) du client qui a passé la commande
    • Le numéro de la commande
  2. Un client sans commande apparaît-il dans un INNER JOIN entre clients et commandes ?

    • Oui, avec des NULL
    • Non
    • Oui, une seule fois
  3. Que se passe-t-il si on oublie la condition de jointure (FROM a, b sans WHERE) ?

    • Une erreur
    • Un produit cartésien : toutes les combinaisons de lignes
    • Seules les lignes identiques sont gardées

La suite du cours

  1. Garder toutes les lignes : LEFT JOIN et anti-jointure18 min
  2. Jointures multiples et auto-jointure18 min
  3. Les sous-requêtes : scalaires, IN, EXISTS et corrélées20 min
  4. Logique conditionnelle et opérations ensemblistes : CASE, UNION, EXCEPT18 min

SQL intermédiaire (jointures, sous-requêtes) · Intermédiaire · 2 h 32