Aller au contenu principal

Les requêtes SQL : créer, modifier, interroger et joindre

Ce que ce chapitre apporte 6 points
  • Créer une table avec ses types, ses contraintes et ses clés étrangères, et voir le moteur refuser ce qui les viole.
  • Ajouter, modifier et supprimer des lignes avec INSERT, UPDATE et DELETE, en vérifiant toujours le WHERE.
  • Lire une table avec SELECT : filtrer, combiner des conditions, trier, dédoublonner, limiter.
  • Relier deux ou trois tables par une jointure interne ou externe, placer chaque condition dans ON ou dans WHERE, et prévoir le nombre de lignes rendues.
  • Expliquer par l'ordre logique d'évaluation pourquoi un alias défini dans SELECT est inconnu de WHERE.
  • Savoir que la page exécute SQLite quand le cours écrit du MySQL, et ce que cela change.

Une requête SQL se lit dans l'ordre FROM, WHERE, SELECT, et la jointure est ce qui relie deux tables. Jusqu'ici on a modélisé et raisonné sur les données. Ici on leur parle : créer les tables, y écrire des lignes, les relire, les relier entre elles. SQL est un langage déclaratif : on décrit le résultat voulu, pas la marche à suivre, et c'est ce qui le rend à la fois court à écrire et déroutant au début. Les requêtes de ce chapitre s'exécutent directement dans la page, sur une base de démonstration : lire, modifier, relancer. Résumer des lignes par paquets, imbriquer des requêtes et nommer une requête viennent ensuite, dans le chapitre sur le SQL avancé. Ce chapitre s'appuie sur les tables et les clés étrangères du chapitre sur le modèle relationnel, et sur la sélection, la projection et la jointure de l'algèbre relationnelle.

Les requêtes de ce chapitre s'exécutent

Chaque bloc SQL a un bouton Exécuter et un bouton Schéma. Modifier la requête, relancer, regarder ce qui change : c'est de loin la façon la plus rapide d'apprendre le langage. La base repart neuve à chaque exécution.

Le moteur de la page est SQLite, un SGBD embarqué dans le navigateur, et non MySQL, que le cours installe dans le chapitre sur l'installation, où un tableau recense les différences qui comptent. Les blocs de ce chapitre rendent les mêmes lignes dans l'un et dans l'autre ; les commandes propres à MySQL (CREATE DATABASE, USE, AUTO_INCREMENT, ENUM, MODIFY, TRUNCATE) sont montrées sans bouton Exécuter. SQLite accepte aussi quelques écritures que MySQL refuse, signalées là où elles mordent : une requête qui passe dans la page n'est pas forcément juste, la règle est celle du cours.

Ce que SQL sait faire

Définition

SQL (Structured Query Language) est un langage déclaratif utilisé pour interagir avec des bases de données relationnelles. Il permet de définir des structures, de manipuler des données, et d'interroger des informations.

Le langage est normalisé, et reconnu par tous les grands SGBD (MySQL, PostgreSQL, Oracle, SQL Server, SQLite), chacun avec son dialecte. Ce cours écrit celui de MySQL.

Choisir le type d'une colonne

Type (MySQL)Description
INT, BIGINTNombres entiers (positifs ou négatifs)
SMALLINT, TINYINTEntiers de plus petite taille (moins de mémoire utilisée)
DECIMAL(p,s)Nombres décimaux exacts, avec p chiffres au total et s après la virgule
FLOAT, REALNombres à virgule flottante, approchés (0,1 n'y est pas exact) : jamais pour un montant
CHAR(n)Texte fixe de longueur n (utile pour des codes, comme un code postal)
VARCHAR(n)Texte variable jusqu'à n caractères
TEXTTexte long (commentaires, descriptions...)
DATEDate au format AAAA-MM-JJ
TIME, DATETIMEHeure seule ou date avec heure
BOOLEANValeur logique ; sous MySQL, un synonyme de TINYINT(1), où TRUE vaut 1 et FALSE vaut 0
ENUM(...)Valeur choisie parmi une liste définie (ex : 'Homme', 'Femme')
BLOBDonnées binaires (images, fichiers, sons...)
Exemples de déclarations
  • nom VARCHAR(50) : chaîne de texte pouvant aller jusqu'à 50 caractères.
  • montant DECIMAL(8,2) : valeur numérique avec 6 chiffres avant la virgule et 2 après (ex : 123456.78).
  • sexe ENUM('Homme', 'Femme', 'Autre') : contraint la valeur à une liste prédéfinie.
  • codePostal CHAR(5) : champ texte fixe pour un code à 5 chiffres.
  • photo BLOB : stocke une image en base de données (rarement conseillé).

CHAR ou VARCHAR ?

Utiliser CHAR(n) quand la valeur a toujours exactement la même taille (code postal, numéro INSEE, identifiant fixe).
Utiliser VARCHAR(n) pour les textes de longueur variable (noms, adresses, titres).
CHAR dit au lecteur du schéma que la taille est fixe ; VARCHAR n'occupe que la place du texte.

Cinq familles d'instructions

Le langage SQL est divisé en plusieurs familles de commandes, selon leur rôle dans la gestion d'une base de données relationnelle.

CatégorieSignificationExemples
DDL (Data Definition Language)Définir ou modifier la structure des objets (tables, vues, clés).CREATE, ALTER, DROP
DML (Data Manipulation Language)Ajouter, modifier ou supprimer des données.INSERT, UPDATE, DELETE
DQL (Data Query Language)Interroger les données pour les lire ou les analyser.SELECT
DCL (Data Control Language)Gérer les permissions et la sécurité d'accès.GRANT, REVOKE
TCL (Transaction Control Language)Contrôler les transactions (groupes d'opérations atomiques).COMMIT, ROLLBACK

Le classement en DQL est courant dans les cours ; la documentation de MySQL, elle, range SELECT parmi les instructions du DML. Ce chapitre pratique le DDL, le DML et le DQL ; le chapitre Les transactions et les propriétés ACID traite le TCL, et montre ce qui se passe quand deux clients écrivent en même temps ; le chapitre Exploiter une base traite le DCL, avec les comptes et les droits.

Structure d'une base de données

Création d'une base de données

SQL
CREATE DATABASE Ecole;
USE Ecole;
Explication

La commande CREATE DATABASE crée une base nommée Ecole. La commande USE permet de travailler à l'intérieur de cette base. Toutes les commandes suivantes s'appliqueront à cette base tant qu'on ne change pas de contexte.

Création d'une table

SQL
CREATE TABLE Etudiant (
idEtudiant INT PRIMARY KEY AUTO_INCREMENT,
nom VARCHAR(50) NOT NULL,
prenom VARCHAR(50) NOT NULL,
dateNaissance DATE,
email VARCHAR(100) UNIQUE,
actif BOOLEAN DEFAULT TRUE
);
Explication des contraintes
  • PRIMARY KEY : identifie chaque ligne de façon unique.
  • AUTO_INCREMENT : la valeur de idEtudiant est générée automatiquement (1, 2, 3…). Plus besoin de la saisir manuellement.
  • NOT NULL : le champ est obligatoire, il ne peut pas rester vide.
  • UNIQUE : deux lignes ne peuvent pas avoir la même valeur (ici, deux étudiants ne peuvent pas avoir le même email). Une valeur absente, NULL, n'est égale à rien : plusieurs étudiants peuvent n'avoir aucun email enregistré. Deux chaînes vides, '', seraient en revanche refusées : une chaîne vide est une valeur.
  • DEFAULT TRUE : si on n'indique pas de valeur pour actif, il vaut TRUE, c'est-à-dire 1.

Ce que la table contient une fois remplie. Les contraintes ne se voient pas dans les lignes, elles se voient dans ce qui a été refusé à l'insertion. Voici deux lignes acceptées :

idEtudiantnomprenomdateNaissanceemailactif
1MartinAlice2001-05-12alice@ecole.fr1
2DurandLucas2000-11-03lucas@ecole.fr1

La base de démonstration de la page reprend cette table avec deux colonnes de plus, ville et idClasse (le bouton Schéma les montre), et d'autres étudiants que ceux du tableau ci-dessus : dix, de Lea Martin à Sofia Petit.

Autres contraintes utiles

SQL
CREATE TABLE Cours (
idCours INT PRIMARY KEY AUTO_INCREMENT,
intitule VARCHAR(100) NOT NULL,
volumeHoraire INT CHECK (volumeHoraire > 0),
coefficient DECIMAL(3,1) DEFAULT 1.0
);
Contraintes supplémentaires
  • CHECK : impose une condition logique sur la valeur (ici, le volume horaire doit être positif). MySQL l'applique depuis sa version 8.0.16 ; avant, il l'acceptait sans la vérifier. Une valeur NULL passe le contrôle, faute d'être fausse : NOT NULL reste nécessaire si la colonne est obligatoire.
  • DEFAULT 1.0 : si aucun coefficient n'est précisé à l'insertion, la valeur 1.0 est utilisée automatiquement.

La table Cours de la page porte la même contrainte, avec une colonne de plus, idEnseignant. Le bloc suivant essaie d'y créer un cours sans aucune heure :

requete.sql
Résultat
>_ Prêt à exécuter…

Le bloc s'arrête sur CHECK constraint failed, et MySQL refuse la même ligne (Check constraint ... is violated). Remplacer 0 par NULL et relancer : la ligne passe, puisqu'une valeur inconnue n'est pas fausse.

Modifier la structure d'une table : ALTER TABLE

ALTER TABLE, du DDL lui aussi, fait évoluer la structure d'une table sans la supprimer ni perdre ses lignes ; seule la suppression d'une colonne détruit les valeurs qu'elle portait.

SQL
-- Ajouter une colonne
ALTER TABLE Etudiant ADD COLUMN telephone VARCHAR(15);
-- Modifier le type d'une colonne
ALTER TABLE Etudiant MODIFY COLUMN telephone VARCHAR(20);
-- Supprimer une colonne
ALTER TABLE Etudiant DROP COLUMN telephone;
-- Renommer une colonne (MySQL 8.0 et suivants)
ALTER TABLE Etudiant RENAME COLUMN actif TO estActif;

Manipuler les données avec SQL

Insertion de données : INSERT

requete.sql
Résultat
>_ Prêt à exécuter…

Une ligne est ajoutée à Etudiant. idEtudiant n'est pas précisé, il est généré automatiquement ; les colonnes non mentionnées prennent leur valeur par défaut : actif vaut 1, et ville et idClasse, qui n'en ont pas, valent NULL.

Le bloc n'affiche aucun tableau, et c'est normal : un INSERT modifie la table et ne rend aucune ligne. La page annonce seulement « 1 ligne modifiée ».

Insérer plusieurs lignes à la fois :

requete.sql
Résultat
>_ Prêt à exécuter…

La page annonce « 2 lignes modifiées » : Pierre Dupont et Sophie Martin reçoivent les identifiants 11 et 12, dans l'ordre de la liste, et la table compte désormais douze étudiants.

Sans nommer les colonnes (déconseillé) :

SQL
-- Déconseillé : fragile si la structure de la table change
INSERT INTO Etudiant
VALUES (NULL, 'Lefevre', 'Luc', '2001-11-30', NULL, TRUE);

Sur la base de la page, qui compte huit colonnes, cette insertion échoue d'ailleurs : « table Etudiant has 8 columns but 6 values were supplied ». C'est exactement le risque d'une liste de colonnes omise, le jour où une table en gagne une : nommer les colonnes rend l'ordre robuste à ce changement.

Lecture des données : SELECT

Une requête s'écrit SELECT en tête, mais se construit dans un autre ordre : FROM d'abord, d'où viennent les lignes ; WHERE ensuite, lesquelles garder, avec toutes les colonnes de la table disponibles pour le filtre, même celles qui ne seront pas affichées ; SELECT en dernier, lesquelles afficher. La fin du chapitre montre que c'est l'ordre dans lequel la requête est évaluée.

requete.sql
Résultat
>_ Prêt à exécuter…

Construite dans l'ordre : partir de Etudiant, ne garder que les étudiants nés après le 1er janvier 2001, n'afficher que le nom et le prénom, trier par nom croissant.

Le résultat contient dix lignes et deux colonnes, c'est-à-dire toute la table : sur cette base de démonstration, aucun étudiant n'est né avant 2001, et le filtre n'écarte donc personne. Remplacer 2001 par 2004 dans le bloc et relancer : le résultat tombe à cinq lignes. Un filtre qui ne retire rien n'est pas un filtre qui ne marche pas.

Opérateurs de filtre courants

OpérateurDescriptionExemple
=, <> (ou !=), <, >Comparaison classiqueWHERE note > 10
BETWEEN ... AND ...Dans un intervalle (bornes incluses)WHERE note BETWEEN 10 AND 15
IN (...)Parmi une liste de valeursWHERE ville IN ('Lyon', 'Paris')
LIKECorrespondance avec un motifWHERE nom LIKE 'Du%'
IS NULL / IS NOT NULLValeur absente ou présenteWHERE email IS NOT NULL
requete.sql
Résultat
>_ Prêt à exécuter…

Le bloc contient trois requêtes et affiche donc trois tableaux. La première rend deux lignes, Dupond et Durand. La deuxième en rend deux également, Bernard et Marchand, les deux étudiants dont l'email est absent. La troisième rend onze lignes sur les vingt-cinq inscriptions : les bornes sont incluses, une note de 10 ou de 15 est retenue.

Les jokers de LIKE.

% remplace n'importe quelle suite de caractères (y compris vide).
_ remplace exactement un caractère, et le reste du motif doit coïncider.
Exemples : 'Du%' trouve les noms qui commencent par « Du », '%on' ceux qui finissent par « on ». 'D_pont' trouve « Dupont » et « Dapont », mais ni « Damont », dont la fin diffère, ni « Duupont », qui a deux lettres à la place du joker.

Majuscules et accents. Dans la page, une comparaison de texte par = distingue les majuscules, les minuscules et les accents : WHERE ville = 'paris' ne trouve personne, et WHERE intitule = 'Mathématiques' non plus, puisque la base écrit Mathematiques, sans accent. MySQL, avec ses réglages d'usine, ne fait aucune de ces distinctions, et trouverait les quatre Parisiens. Écrire la valeur exactement comme elle est stockée donne le même résultat partout. LIKE, lui, ignore la casse dans les deux moteurs : 'du%' trouve encore Dupond et Durand. MySQL ignore aussi les accents, et la page non : intitule LIKE 'mathé%' trouve les mathématiques sous MySQL, et aucun cours dans la page. Les noms de table suivent une autre règle : sous Linux, MySQL distingue Etudiant de etudiant, quand la page les confond.

Combiner des conditions : AND, OR et les parenthèses

AND est prioritaire sur OR, exactement comme la multiplication l'est sur l'addition. Les deux requêtes suivantes ne diffèrent que par une paire de parenthèses.

requete.sql
Résultat
>_ Prêt à exécuter…

La première rend les quatre Parisiens, tous actifs. La seconde en rend six : les mêmes, plus Tom Durand et Sofia Petit, qui habitent Nantes et ne sont plus actifs. Sans parenthèses, la condition actif = 1 ne s'applique qu'à Paris. Au moindre mélange de AND et de OR, écrire les parenthèses, même quand on croit connaître la priorité.

Afficher des valeurs distinctes : DISTINCT

requete.sql
Résultat
>_ Prêt à exécuter…

DISTINCT supprime les doublons du résultat. Client compte quatre lignes et deux clients habitent Paris ; le résultat contient trois lignes et une colonne, Paris, Lyon et Bordeaux. Le dédoublonnage coûte cher au moteur, qui ne le fait que si on le demande.

Limiter le nombre de résultats : LIMIT

requete.sql
Résultat
>_ Prêt à exécuter…

LIMIT coupe le résultat après le nombre de lignes indiqué, une fois le tri appliqué. Ici la table compte exactement dix étudiants : le résultat en contient dix et la coupe ne retire rien. Passer à LIMIT 3 pour la voir agir, ce qui laisse Bernard, Diallo et Dupond, les trois premiers dans l'ordre alphabétique. Sans ORDER BY, la coupe porterait sur des lignes quelconques. Trier puis couper répond à toute question de classement.

Exercice : écrire puis vérifier

Les trois étudiants les plus jeunes

Une enquête sur l'entrée dans les études supérieures interroge les trois plus jeunes étudiants de l'école. Écrire une requête qui renvoie leur nom, leur prénom et leur date de naissance, du plus jeune au moins jeune.

« Exécuter » montre ce que rend la requête ; « Vérifier » compare son résultat à celui attendu, sur 2 cas, dont 1 caché.

Mise à jour : UPDATE

requete.sql
Résultat
>_ Prêt à exécuter…

Le nom et l'email de l'étudiant 2 sont modifiés. Plusieurs colonnes se modifient d'un coup, séparées par des virgules dans SET.

Comme pour l'insertion, aucun tableau ne s'affiche : le bloc annonce « 1 ligne modifiée », rien de plus. Le seul moyen de vérifier une modification est de relire la table ensuite.

À manipuler
Ajouter sous l'UPDATE la ligne SELECT idEtudiant, nom, prenom, email FROM Etudiant WHERE idEtudiant = 2; et relancer : Amine Diallo s'appelle désormais Amine Dupond. Retirer ce SELECT, remplacer 2 par 99 dans le WHERE et relancer : « 0 ligne modifiée », sans aucune erreur. Un WHERE qui ne vise personne n'est signalé que par ce compte.

Règle d'or du UPDATE. Toujours utiliser WHERE pour cibler les lignes à modifier. Un UPDATE Etudiant SET nom = 'Dupond' sans WHERE modifie tous les étudiants de la table, et cette erreur est irrécupérable sans sauvegarde.

MySQL Workbench active par défaut l'option « Safe Updates », qui refuse un UPDATE ou un DELETE dont le WHERE ne porte pas sur une clé. C'est un garde-fou à garder, pas une gêne à désactiver.

Suppression : DELETE

requete.sql
Résultat
>_ Prêt à exécuter…

Seul l'étudiant 4, Tom Durand, est supprimé de la table Etudiant. Filtrer sur la clé primaire est la façon la plus sûre de ne viser qu'une ligne, puisqu'aucune autre ne peut porter la même valeur.

Là encore, le bloc ne rend aucun tableau : il annonce « 1 ligne modifiée ». Ce compte ne dit pas tout. Les deux inscriptions de Tom ont disparu avec lui, parce que la table Inscription le demande par sa clé étrangère (ON DELETE CASCADE, présentée plus bas) ; le moteur ne compte que les lignes visées par l'ordre lui-même. L'ordre ne visait qu'une ligne, et en a fait disparaître trois.

Supprimer toutes les données de la table (dangereux) :

SQL
-- Supprime toutes les lignes, une par une
DELETE FROM Etudiant;
-- Supprime et recrée la table, et remet l'AUTO_INCREMENT à sa valeur de départ
TRUNCATE TABLE Etudiant;
DELETE ou TRUNCATE

DELETE sans WHERE vide la table et garde sa structure. Il ne s'annule par ROLLBACK que s'il a été lancé dans une transaction ouverte au préalable (START TRANSACTION) : par défaut, MySQL valide chaque instruction dès qu'elle se termine. Le chapitre sur les transactions explique ce mécanisme.

TRUNCATE, bien plus rapide sur une grosse table, valide la transaction en cours et ne s'annule jamais. MySQL le refuse sur une table qu'une clé étrangère d'une autre table référence : tant qu'Inscription pointe vers Etudiant, TRUNCATE TABLE Etudiant échoue.

Une fois validés, ni l'un ni l'autre ne se rattrape sans sauvegarde. Le moteur de cette page ne connaît pas TRUNCATE.

Vérification rapideon peut se reprendre

1.Que rend un UPDATE exécuté dans la page ?

2.DELETE FROM Etudiant;, sans clause WHERE, est-elle une instruction valide ?

La règle d'or se met à l'essai sur une suppression dont le WHERE cache un piège.

Exercice : écrire puis vérifier

Retirer les inscriptions restées sans note

En fin d'année, une inscription restée sans note est un abandon, et la scolarité la retire avant d'éditer les relevés. Écrire l'ordre qui supprime de la table Inscription les inscriptions dont la note n'est pas renseignée, et elles seules. Une note de 0 est une note : elle reste.

La vérification joue l'ordre, puis relit toutes les inscriptions.

« Exécuter » joue le script et montre l'état de la base qu'il laisse ; « Vérifier » compare cet état à celui attendu, sur 3 cas, dont 1 caché.

Relier deux tables : la clé étrangère

Une inscription relie un étudiant à un cours. La table qui la porte désigne l'un et l'autre par leur identifiant, et déclare ce lien pour que la base le fasse respecter.

requete.sql
Résultat
>_ Prêt à exécuter…
Explication
  • Clé primaire composite : le couple (idEtudiant, idCours) est unique : un étudiant ne peut s'inscrire qu'une seule fois à un même cours.
  • NOT NULL sur les colonnes de la clé : MySQL l'ajoute de lui-même à toute colonne d'une clé primaire ; SQLite non, et accepterait sans lui une inscription sans étudiant. L'écrire rend la règle visible, et vraie sur les deux moteurs.
  • FOREIGN KEY : crée un lien vers une autre table. Le SGBD vérifie que la valeur existe bien dans la table référencée.
  • ON DELETE CASCADE : si un étudiant est supprimé, ses inscriptions sont automatiquement supprimées aussi.
  • ON DELETE RESTRICT : si un cours est supprimé mais a des inscrits, la suppression est bloquée. Protection contre les suppressions accidentelles.

Sous MySQL, Etudiant et Cours doivent exister avant Inscription : une clé étrangère ne peut viser qu'une table déjà créée, et la création échoue sinon (Failed to open the referenced table). On crée donc d'abord les tables que d'autres désignent. Ce bloc part d'une base vide dans la page, et SQLite, plus souple, accepte la déclaration avant que les deux tables existent.

Ce que garantit l'intégrité référentielle. La table Inscription de la page est déclarée ainsi. Le bloc suivant y insère deux lignes, dont la seconde désigne un étudiant qui n'existe pas :

requete.sql
Résultat
>_ Prêt à exécuter…

Le bloc s'arrête sur FOREIGN KEY constraint failed : la seconde ligne n'est jamais écrite. MySQL refuse la même insertion, avec le message Cannot add or update a child row: a foreign key constraint fails. SQLite, lui, ne vérifie les clés étrangères que si on le lui demande ; la page le lui demande à chaque exécution, pour se comporter comme MySQL, qui les vérifie toujours. Une contrainte que le moteur ne vérifie pas est une contrainte que personne ne respecte.

Les deux règles de suppression se voient aussi. Supprimer un étudiant emporte ses inscriptions :

requete.sql
Résultat
>_ Prêt à exécuter…

Le premier compte vaut 2, le second 0. Supprimer un cours qui a des inscrits, en revanche, est refusé :

requete.sql
Résultat
>_ Prêt à exécuter…

Le moteur répond de nouveau FOREIGN KEY constraint failed ; MySQL écrit Cannot delete or update a parent row. Pour supprimer ce cours, il faudrait d'abord supprimer ses inscriptions, ce qui oblige à le faire exprès.

Vérification rapideon peut se reprendre

Quelle contrainte garantit qu'une colonne ne contient jamais deux fois la même valeur, tout en acceptant qu'elle soit absente ?

Requêtes avec jointures

Une jointure permet de combiner des données provenant de plusieurs tables. C'est l'opération clé dans une base relationnelle, où les informations sont réparties sur plusieurs tables reliées par des clés. Dans un tableur, c'est ce que fait RECHERCHEV, une ligne à la fois et une colonne à la fois ; le chapitre Excel sur la recherche en montre les limites, que la jointure n'a pas.

Données d'exemple

Les jointures s'illustrent sur deux petites tables :

Table Etudiant :

idEtudiantnomprenom
1MartinAlice
2DurandLucas
3LeroyEmma

Table Inscription (le cours 10 est « Maths », le 20 est « SQL ») :

idEtudiantidCours
110
120
210

Emma (id=3) n'a aucune inscription. Alice (id=1) en a deux. Ce sont ces deux particularités qui distinguent les types de jointure. La figure suivante joint les deux tables des cinq façons possibles : l'appariement ne change jamais, seul change le sort des lignes qui n'ont trouvé personne.

INNER JOIN … ON ne garde que les lignes appariées des deux côtés.

Etudiant(gauche, 3 lignes)

idEtudiantnomprenom
1MartinAlice
2DurandLucas
3LeroyEmma

Inscription(droite, 3 lignes)

idEtudiantidCours
110
120
210

ON Etudiant.idEtudiant = Inscription.idEtudiant

Résultat3 lignes = 3 appariées
Etudiant.idEtudiantnomprenomInscription.idEtudiantidCours
1MartinAlice110
1MartinAlice120
2DurandLucas210
  • Une ligne de gauche appariée à plusieurs lignes de droite se répète autant de fois : une jointure peut rendre plus de lignes qu'aucune des deux tables n'en contient.
SELECT *
FROM Etudiant
INNER JOIN Inscription
  ON Etudiant.idEtudiant = Inscription.idEtudiant
Les cinq jointures sur les mêmes données. Les lignes colorées trouvent une correspondance, les pâles n'en trouvent pas. Un NULL en orange a été fabriqué par la jointure : il n'existe nulle part dans la base.
À manipuler
Passer de INNER à LEFT : le décompte passe de « 3 lignes = 3 appariées » à « 4 lignes = 3 appariées + 1 de Etudiant sans correspondance », et la ligne d'Emma, pâle en INNER, est gardée. Passer à RIGHT : 3 lignes, puisque chaque inscription désigne un étudiant qui existe. Passer à CROSS : 9 lignes, 3 × 3, dont six associent un étudiant à une inscription qui n'est pas la sienne.

Alice, elle, figure deux fois dans chaque jointure, une par inscription : une jointure peut rendre plus de lignes que la table de départ n'en contient. Quant aux NULL qui complètent Emma en LEFT, aucune ligne de la base ne les contient : la jointure les invente pour combler une ligne qui n'existe pas, et ils faussent souvent les calculs qui suivent.

Jointure interne (INNER JOIN)

requete.sql
Résultat
>_ Prêt à exécuter…

Construite dans l'ordre : partir de Etudiant, l'apparier à Inscription sur l'identifiant d'étudiant, puis n'afficher que le nom, le prénom et le cours.

Sur les trois étudiants de la figure ci-dessus, le résultat compterait trois lignes :

nomprenomidCours
MartinAlice10
MartinAlice20
DurandLucas10

Une JOIN (ou INNER JOIN) ne conserve que les lignes ayant une correspondance dans les deux tables. Le bloc, lui, s'exécute sur la base de démonstration, qui compte dix étudiants et vingt-cinq inscriptions : il rend vingt-cinq lignes et trois colonnes, une par inscription appariée.

Vérification rapideon peut se reprendre

Que fait une jointure interne aux lignes qui n'ont pas de correspondance ?

Jointure externe gauche (LEFT JOIN)

requete.sql
Résultat
>_ Prêt à exécuter…

Sur les données de la figure, le résultat compterait quatre lignes, une de plus que la jointure interne :

nomprenomidCours
MartinAlice10
MartinAlice20
DurandLucas10
LeroyEmmaNULL

Une LEFT JOIN conserve toutes les lignes de la table de gauche (Etudiant), même sans correspondance à droite. Exécuté sur la base de démonstration, ce bloc rend pourtant les mêmes vingt-cinq lignes que le précédent : aucun étudiant de cette base n'est sans inscription, il n'y a aucune orpheline à rattraper. La différence ne se voit que lorsqu'une ligne reste seule, et c'est pour cela que la figure invente Emma.

Une condition sur la table de droite : dans ON, pas dans WHERE

La scolarité veut la liste de tous les étudiants, chacun avec sa note de mathématiques s'il en a une. La première idée ajoute la condition sur le cours dans WHERE :

requete.sql
Résultat
>_ Prêt à exécuter…

Cinq lignes, et non dix : les cinq étudiants qui ne suivent pas les mathématiques ont disparu. La jointure externe les avait pourtant gardés, une ligne par cours suivi, ou une ligne de NULL pour qui n'en suit aucun. Puis WHERE passe sur ces lignes assemblées, et i.idCours = 3 y est faux pour un autre cours, inconnu sur une ligne de NULL : dans les deux cas la ligne tombe. La jointure externe n'a servi à rien, la requête rend ce que rendrait une jointure interne.

requete.sql
Résultat
>_ Prêt à exécuter…

Dix lignes : les cinq notes de mathématiques, et cinq NULL pour Durand, Lefebvre, Moreau, Marchand et Petit. Dans ON, la condition ne trie plus le résultat : elle dit quelles inscriptions s'associent à un étudiant. Un étudiant sans inscription en mathématiques n'en reçoit aucune, et la jointure externe le garde, avec des NULL. Dans une jointure externe, une condition sur la table de droite se met donc dans ON ; une condition sur la table de gauche, comme e.actif = 1, va dans WHERE, comme d'habitude.

À manipuler
Ajouter WHERE i.idCours IS NULL à la fin du second bloc, avant le point-virgule, et relancer : il reste les cinq étudiants qui ne suivent pas les mathématiques. Cette fois, le WHERE qui passe sur les lignes assemblées est voulu : idCours ne peut pas être NULL dans une vraie inscription, le test ne garde donc que les lignes fabriquées par la jointure externe.

La même règle, sur d'autres tables :

Exercice : écrire puis vérifier

Chaque enseignant, et ses cours de plus de 25 heures

Écrire une requête qui renvoie le nom de chaque enseignant (nomEns), puis l'intitulé et le volume horaire de chacun de ses cours de plus de 25 heures.

Un enseignant qui n'en a aucun figure une fois, avec des cases vides. L'ordre des lignes n'a pas d'importance.

« Exécuter » montre ce que rend la requête ; « Vérifier » compare son résultat à celui attendu, sur 3 cas, dont 1 caché.

Jointure sur 3 tables

requete.sql
Résultat
>_ Prêt à exécuter…

Les jointures se chaînent pour remonter les données à travers la table de liaison Inscription : d'abord l'étudiant vers ses inscriptions, puis chaque inscription vers son cours. Le résultat contient vingt-cinq lignes et deux colonnes, le nom de l'étudiant et l'intitulé du cours. Un étudiant y figure autant de fois qu'il suit de cours, ce qu'il faut garder en tête avant de compter quoi que ce soit sur ce résultat. Un compte fait sur une jointure peut tomber juste sur certaines données, et faux sur d'autres.

Compter les étudiants de Claire Durand

Une seule étape est fausse. Désigner laquelle.

Claire Durand enseigne deux cours, les bases de données (cours 1) et l'informatique (cours 5). La scolarité veut savoir combien d'étudiants différents suivent au moins l'un des deux, et raisonne par étapes. Un même étudiant peut suivre nn de ces deux cours, de 0 à 2.

Une table jointe à elle-même

La base de la page contient aussi une table Employe, avec le service de chacun. Pour associer chaque employé à ses collègues de service, la table se joint à elle-même :

requete.sql
Résultat
>_ Prêt à exécuter…

Quatre lignes, chaque paire dans les deux sens : Girard et Barbier aux Etudes, Lemoine et Roche au Support. Vidal, seule au Commerce, n'a personne. Les alias a et b font de la même table deux sources distinctes : sans eux, Employe.service ne désignerait aucune des deux. La condition b.idEmploye <> a.idEmploye écarte l'employé apparié à lui-même.

Résumé des types de jointures

Type de jointureRésultat attenduSur la figure ci-dessus
INNER JOINseulement les lignes appariées des deux côtés3 lignes
LEFT JOINtoutes les lignes de gauche, droite complétée par des NULL4 lignes, Emma revient
RIGHT JOINtoutes les lignes de droite, gauche complétée par des NULL3 lignes, aucune inscription n'est orpheline
FULL JOINtoutes les lignes des deux tables4 lignes ; absente de MySQL, qui l'écrit par l'union d'un LEFT JOIN et d'un RIGHT JOIN, mais acceptée par la page
CROSS JOINchaque ligne de gauche avec chaque ligne de droite, sans condition9 lignes, 3 × 3

RIGHT JOIN sert peu : la même requête s'écrit plus lisiblement en inversant les deux tables autour d'un LEFT JOIN.

Vérification rapideon peut se reprendre

1.Une ligne de gauche s'apparie à trois lignes de droite. Combien de lignes produit-elle ?

2.D'où vient un NULL apparu dans le résultat d'un LEFT JOIN, à droite ?

3.Une jointure sur trois tables passe par une table de liaison. Pourquoi ?

Prévoir avant d'exécuter

Le nombre de lignes d'une jointure se prévoit à partir de ce que l'on sait des tables, avant de lancer quoi que ce soit. Les quatre questions suivantes portent sur les cours et les enseignants de la base : 7 cours, dont 2 sans enseignant, et 3 enseignants.

Prévoir le nombre de lignes d'une jointure

  • 1.

    La base compte 7 cours et 3 enseignants. Combien de lignes rend SELECT * FROM Cours CROSS JOIN Enseignant ?

  • 2.

    Deux cours, Physique et Reseaux, n'ont pas d'enseignant, et chacun des autres en a exactement un. Combien de lignes rend Cours JOIN Enseignant ON Cours.idEnseignant = Enseignant.idEnseignant ?

  • 3.

    Et la même jointure écrite LEFT JOIN, avec Cours à gauche ?

  • 4.

    Une enseignante est recrutée, sans aucun cours. Claire Durand a deux cours, Marc Ferrand un, Sophie Aubert deux. Combien de lignes rend Enseignant LEFT JOIN Cours, avec Enseignant à gauche ?

Mettre deux résultats bout à bout : UNION

L'annuaire de l'école réunit les étudiants et les enseignants, deux tables aux colonnes différentes. Deux requêtes qui rendent autant de colonnes l'une que l'autre se combinent comme deux ensembles, par l'union, l'intersection et la différence du chapitre sur l'algèbre.

requete.sql
Résultat
>_ Prêt à exécuter…

Douze noms dans le premier tableau, treize dans le second : UNION retire les doublons, et Durand, porté par Tom l'étudiant et par Claire l'enseignante, n'y figure qu'une fois. Il compare des lignes entières : avec le prénom, le troisième tableau retrouve les treize personnes. INTERSECT garde le seul nom commun, Durand ; EXCEPT, les neuf noms d'étudiants qu'aucun enseignant ne porte. Les colonnes s'apparient par leur position, jamais par leur nom : unir nom, prenom à prenomEns, nomEns s'exécute sans erreur, et range Claire parmi les noms de famille. MySQL connaît INTERSECT et EXCEPT depuis sa version 8.0.31.

L'ordre logique d'évaluation d'une requête

L'ordre de construction annoncé au début du chapitre, FROM puis WHERE puis SELECT, n'était pas un conseil de rédaction : c'est l'ordre logique dans lequel une requête est évaluée. Le moteur est libre de s'y prendre autrement pour aller plus vite, en filtrant avant de joindre ou en passant par un index, mais son résultat est toujours celui de cet ordre. Le voici en entier, avec les clauses vues depuis et les deux du chapitre suivant.

Ordre logiqueClauseRôle
1FROM + JOINCharge les tables et les assemble
2WHEREFiltre les lignes
3GROUP BYRegroupe les lignes restantes
4HAVINGFiltre les groupes
5SELECT (et DISTINCT)Calcule les colonnes affichées, puis supprime les doublons si DISTINCT le demande
6ORDER BYTrie les résultats
7LIMITLimite le nombre de lignes retournées

GROUP BY et HAVING, qui forment des paquets de lignes et les filtrent, sont l'objet du chapitre sur le SQL avancé. La figure suivante fait tourner une requête sans regroupement, étape par étape, et montre la relation obtenue après chaque clause.

SELECT nom, villeFROM EtudiantWHERE actif = 1ORDER BY nomLIMIT 3

Ordre logique d'évaluation. Cliquer sur une étape pour voir ce qu'elle produit.

7. LIMITn'en garde qu'un début3 lignes
nomville
BernardParis
DialloLyon
DupondParis
WHERE travaille sur des lignes complètes, où la colonne actif existe encore. SELECT ne garde ensuite que deux colonnes, ORDER BY les trie, LIMIT coupe après trois.
À manipuler
Cliquer sur chaque étape, de FROM à LIMIT, en notant le nombre de lignes et de colonnes : 10 lignes et 8 colonnes après FROM, 8 lignes après WHERE, actif toujours présente, 8 lignes et 2 colonnes après SELECT, 3 après LIMIT. Le nombre de lignes ne baisse qu'à WHERE et à LIMIT : sans DISTINCT, SELECT retire des colonnes, jamais des lignes.

Ce que ce tableau explique. Un alias défini dans SELECT n'existe pas encore à l'étape 2 : la norme SQL interdit de l'employer dans WHERE, et MySQL le refuse (« Unknown column »). Il est permis dans ORDER BY, qui vient après.

requete.sql
Résultat
>_ Prêt à exécuter…

Deux employés, Barbier à 37 200 et Vidal à 33 000. Le WHERE répète le calcul au lieu de citer annuel ; ORDER BY, évalué après SELECT, cite l'alias.

À manipuler
Remplacer salaire * 12 par annuel dans le WHERE et relancer : la page rend les mêmes deux lignes, Barbier et Vidal, parce que SQLite s'accorde cette liberté. MySQL refuse la même requête, avec le message Unknown column 'annuel' in 'where clause'.

Chaque moteur s'accorde ainsi des libertés : MySQL accepte un alias dans GROUP BY et HAVING, SQLite partout. L'écriture qui répète l'expression passe partout.

Vérification rapideon peut se reprendre

Pourquoi MySQL refuse-t-il dans WHERE un alias défini dans SELECT ?

Ressources complémentaires

Chapitre révisé en octobre 2026 · Une erreur dans ce chapitre ?

Mettre en pratique