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.idest la clé primaire : elle identifie chaque client de façon unique ;commandes.client_idest une clé étrangère : elle contient l’iddu 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
- Identifier les tables qui contiennent les informations voulues.
- Repérer la clé étrangère qui les relie.
- Écrire le
JOIN ... ONet vérifier le nombre de lignes obtenu. - 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.
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;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 ...;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
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
Un client sans commande apparaît-il dans un INNER JOIN entre clients et commandes ?
- Oui, avec des NULL
- Non
- Oui, une seule fois
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
- Garder toutes les lignes : LEFT JOIN et anti-jointure18 min
- Jointures multiples et auto-jointure18 min
- Les sous-requêtes : scalaires, IN, EXISTS et corrélées20 min
- Logique conditionnelle et opérations ensemblistes : CASE, UNION, EXCEPT18 min