Le problème : le nom du client n'est pas dans la commande
Vous voulez afficher « Marie Dupont a commandé pour 89,90 € ». Mais la table commandes ne contient pas le nom de Marie. Elle contient seulement un client_id = 1. Le nom, lui, est dans la table clients.
C'est volontaire. Si on stockait « Marie Dupont » dans chaque commande, et qu'elle se marie et change de nom, il faudrait modifier des milliers de lignes. En séparant, on écrit le nom une seule fois. C'est le principe des relations. Pour rassembler les morceaux au moment de l'affichage, on utilise une jointure.
clients commandes
id | nom | ville id | client_id | montant
---+----------------+------ ---+-----------+--------
1 | Marie Dupont | Besac. 1 | 1 | 89.90
2 | Karim Benali | Dijon 2 | 2 | 42.00
3 | Léa Martin | Lyon 3 | 1 | 120.50
La clé étrangère : le pont entre deux tables
Dans commandes, la colonne client_id est une clé étrangère (foreign key) : elle pointe vers la clé primaire id de la table clients. C'est le fil qui relie une commande à son client.
Une clé primaire identifie une ligne dans SA table. Une clé étrangère est une copie de cet identifiant rangée dans une AUTRE table pour créer le lien. commandes.client_id référence clients.id.
INNER JOIN : ne garder que les correspondances
Un INNER JOIN assemble deux tables sur une condition de correspondance définie par ON. Il ne garde que les lignes qui matchent des deux côtés.
SELECT c.nom, cmd.montant
FROM clients AS c
INNER JOIN commandes AS cmd
ON cmd.client_id = c.id
ORDER BY c.nom;
On lit : « pour chaque commande, va chercher dans clients la ligne dont l'id égale le client_id, et colle les colonnes ensemble ». Marie apparaît deux fois (elle a deux commandes), Léa n'apparaît pas (elle n'a aucune commande).
Toujours qualifier les colonnes avec des alias de table. Écrivez c.nom et cmd.montant plutôt que nom et montant. Si les deux tables ont une colonne du même nom (comme id), une requête non qualifiée est ambiguë et échoue. Les alias rendent aussi la requête bien plus lisible.
LEFT JOIN : garder tout le monde, même sans correspondance
Et si on veut tous les clients, y compris ceux qui n'ont jamais commandé ? L'INNER JOIN les oublie. Le LEFT JOIN garde toutes les lignes de la table de gauche (clients), et met NULL dans les colonnes de droite quand il n'y a pas de correspondance.
SELECT c.nom, cmd.montant
FROM clients AS c
LEFT JOIN commandes AS cmd
ON cmd.client_id = c.id
ORDER BY c.nom;
Cette fois, Léa apparaît avec un montant à NULL. Combiné à une agrégation, le LEFT JOIN répond à « combien chaque client a-t-il dépensé, zéro inclus » :
SELECT c.nom, COALESCE(SUM(cmd.montant), 0) AS total
FROM clients AS c
LEFT JOIN commandes AS cmd
ON cmd.client_id = c.id
GROUP BY c.id, c.nom
ORDER BY total DESC;
COALESCE(valeur, 0) remplace un NULL par 0. Pratique pour qu'un client sans commande affiche un total de 0 au lieu de NULL.
The problem: the customer name is not in the order
You want to display "Marie Dupont ordered for 89.90 EUR". But the commandes table does not contain Marie's name. It only contains a client_id = 1. The name lives in the clients table.
This is on purpose. If we stored "Marie Dupont" in every order, and she married and changed her name, we would have to update thousands of rows. By splitting, we write the name only once. This is the principle of relations. To reassemble the pieces at display time, we use a join.
clients commandes
id | nom | ville id | client_id | montant
---+----------------+------ ---+-----------+--------
1 | Marie Dupont | Besac. 1 | 1 | 89.90
2 | Karim Benali | Dijon 2 | 2 | 42.00
3 | Lea Martin | Lyon 3 | 1 | 120.50
The foreign key: the bridge between two tables
In commandes, the client_id column is a foreign key: it points to the primary key id of the clients table. It is the thread linking an order to its customer.
A primary key identifies a row in ITS table. A foreign key is a copy of that identifier stored in ANOTHER table to create the link. commandes.client_id references clients.id.
INNER JOIN: keep only matches
An INNER JOIN assembles two tables on a match condition defined by ON. It keeps only the rows that match on both sides.
SELECT c.nom, cmd.montant
FROM clients AS c
INNER JOIN commandes AS cmd
ON cmd.client_id = c.id
ORDER BY c.nom;
Read it as: "for each order, fetch from clients the row whose id equals the client_id, and stick the columns together". Marie appears twice (she has two orders), Lea does not appear (she has no order).
Always qualify columns with table aliases. Write c.nom and cmd.montant rather than nom and montant. If both tables have a column with the same name (like id), an unqualified query is ambiguous and fails. Aliases also make the query far more readable.
LEFT JOIN: keep everyone, even without a match
What if we want all customers, including those who never ordered? The INNER JOIN forgets them. The LEFT JOIN keeps every row of the left table (clients), and puts NULL in the right columns when there is no match.
SELECT c.nom, cmd.montant
FROM clients AS c
LEFT JOIN commandes AS cmd
ON cmd.client_id = c.id
ORDER BY c.nom;
This time, Lea appears with a NULL montant. Combined with aggregation, the LEFT JOIN answers "how much did each customer spend, zero included":
SELECT c.nom, COALESCE(SUM(cmd.montant), 0) AS total
FROM clients AS c
LEFT JOIN commandes AS cmd
ON cmd.client_id = c.id
GROUP BY c.id, c.nom
ORDER BY total DESC;
COALESCE(value, 0) replaces a NULL with 0. Handy so a customer with no order shows a total of 0 instead of NULL.
À vous d'essayer — la base est déjà remplie :
CREATE TABLE clients (id INTEGER, nom TEXT);
CREATE TABLE commandes (id INTEGER, client_id INTEGER, montant REAL);
INSERT INTO clients VALUES (1, 'Alice Durand');
INSERT INTO clients VALUES (2, 'Marc Petit');
INSERT INTO commandes VALUES (1, 1, 120.50);
INSERT INTO commandes VALUES (2, 1, 80.00);
INSERT INTO commandes VALUES (3, 2, 45.00);
SELECT clients.nom, commandes.montant
FROM clients
INNER JOIN commandes ON commandes.client_id = clients.id
ORDER BY clients.nom;
Demandez la jointure à l'IA et vérifiez le type (INNER vs LEFT) et les alias de table :
J'ai deux tables : clients (id, nom, ville) et commandes (id, client_id, montant), où commandes.client_id référence clients.id. Écris une requête SQL qui liste TOUS les clients avec le total de leurs achats, y compris ceux qui n'ont jamais commandé (total 0). Utilise des alias de table et qualifie chaque colonne. Précise pourquoi tu choisis LEFT JOIN plutôt qu'INNER JOIN.
Sans relire la réponse de l'IA : avec tes mots, pourquoi a-t-elle choisi LEFT JOIN plutôt qu'INNER JOIN pour lister TOUS les clients, même ceux à 0 € ?
INNER JOIN ne garde que les clients qui ont au moins une commande, donc les clients sans commande disparaissent du résultat. Le LEFT JOIN garde toutes les lignes de la table de gauche (clients) et met NULL à droite quand il n'y a pas de correspondance ; combiné à COALESCE(SUM(...), 0), le client sans achat affiche bien un total de 0 au lieu de disparaître.Écrivez une requête qui affiche le nom du client et le montant de chaque commande, uniquement pour les clients ayant au moins une commande. Utilisez un INNER JOIN, la clause ON (cmd.client_id = c.id) et des alias de table.
Tu lui as demandé : « liste TOUS les clients avec leur total dépensé, ceux sans commande inclus ». L'IA renvoie ceci. Tu l'acceptes tel quel ou tu le rejettes ?
SELECT c.nom, SUM(cmd.montant) AS total
FROM clients AS c
INNER JOIN commandes AS cmd
ON cmd.client_id = c.id
GROUP BY c.id, c.nom
ORDER BY total DESC;
INNER JOIN ne garde que les clients ayant au moins une commande : un client à 0 € disparaît silencieusement du résultat. C'est le piège classique de la jointure, et le plus sournois car la requête tourne sans erreur. Deux corrections : passer en LEFT JOIN pour garder tous les clients, et envelopper la somme dans COALESCE(SUM(cmd.montant), 0) pour afficher 0 au lieu de NULL.Sans remonter dans la leçon : quelle est la différence entre INNER JOIN et LEFT JOIN, et à quoi sert la clause ON ?
INNER JOIN ne garde que les lignes qui ont une correspondance des deux côtés (un client SANS commande disparaît). Le LEFT JOIN garde toutes les lignes de la table de gauche et met NULL à droite quand il n'y a pas de correspondance. La clause ON définit la condition de rapprochement, ici cmd.client_id = c.id : c'est elle qui dit quelles lignes des deux tables vont ensemble.Jusqu'ici vous n'avez fait que lire. La prochaine leçon vous donne les pleins pouvoirs : ajouter, modifier et supprimer des lignes avec INSERT, UPDATE et DELETE, sans vider toute la table par accident.
Leçon 6 : Modifier les données →