Aller au contenu principal

Les requêtes SQL

Ce que ce chapitre apporte4 points
  • Comprendre le rôle du langage SQL dans la gestion des bases de données relationnelles.
  • Créer une base de données et des tables avec des contraintes simples.
  • Manipuler les données avec les instructions SELECT, INSERT, UPDATE et DELETE.
  • Interroger les données avec des filtres, des tris, des jointures simples et des fonctions d'agrégation.

Jusqu'ici on a modélisé et raisonné sur les données. Ici on leur parle. 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.

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.

À quoi sert SQL ?

Les chapitres précédents ont montré comment modéliser une base de données (MCD, MLD) et raisonner sur les données avec l'algèbre relationnelle. SQL est la traduction concrète de tout cela : c'est le langage qu'on tape pour interroger une vraie base de données.

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 rapidement et efficacement.

SQL est omniprésent dans les systèmes informatiques modernes. Il permet à la fois :

  • De créer et structurer une base de données (CREATE, ALTER, DROP) ;
  • D'ajouter, modifier ou supprimer des données (INSERT, UPDATE, DELETE) ;
  • D'interroger et exploiter les données (SELECT, JOIN, GROUP BY, etc.) ;
  • De contrôler les accès et la sécurité (GRANT, REVOKE) ;
  • De générer des statistiques simples (moyennes, totaux, comptages).

Pourquoi le SQL est-il toujours utilisé aujourd'hui ?

  • Standardisé : SQL est un langage reconnu par tous les grands systèmes de gestion de bases de données (MySQL, PostgreSQL, Oracle, SQL Server…).
  • Lisible : sa syntaxe proche du langage naturel le rend facilement compréhensible.
  • Puissant : il permet de traiter efficacement de très grandes quantités de données.
  • Transverse : il est utilisé dans presque tous les métiers liés à la donnée.
Exemples d'application
  • Informatique : authentification, gestion des comptes, suivi des activités utilisateur.
  • BTP : planification des interventions, suivi des livraisons, gestion des équipements.
  • Énergie : gestion des capteurs, alertes sur la consommation, historique des relevés.
  • Transport : géolocalisation des véhicules, affectation des tournées, suivi des colis.
  • Industrie : traçabilité des produits, contrôle qualité, gestion de la maintenance.
À retenir

SQL est un outil essentiel pour manipuler et exploiter efficacement des données relationnelles. Il est utilisé dans tous les secteurs d'activité, quel que soit le volume ou le type d'information à gérer.

Types de données courants

Type SQLDescription
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 précis avec p chiffres au total et s après la virgule
FLOAT, REALNombres décimaux à virgule flottante (moins précis, plus légers)
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 : TRUE ou FALSE
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 est légèrement plus rapide ; VARCHAR économise de l'espace disque.

Familles de commandes SQL

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
Exemples par usage
  • DDL : Créer une nouvelle table dans la base.
  • DML : Ajouter un étudiant ou modifier son prénom.
  • DQL : Rechercher tous les étudiants nés après 2001.
  • DCL : Donner à un utilisateur le droit de modifier la table Etudiant.
  • TCL : Annuler une série d'insertions en cas d'erreur (ROLLBACK). Le chapitre Les transactions y revient en détail, et montre ce qui se passe quand deux clients écrivent en même temps.
À retenir

Les commandes DML et DQL sont les plus courantes pour interagir avec les données au quotidien. Les commandes DDL et DCL sont essentielles pour la structure et la sécurité de la base.

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).
  • DEFAULT TRUE : si on n'indique pas de valeur pour actif, il vaut TRUE par défaut.

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

idEtudiant a été généré automatiquement grâce à AUTO_INCREMENT.

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).
  • DEFAULT 1.0 : si aucun coefficient n'est précisé à l'insertion, la valeur 1.0 est utilisée automatiquement.
À retenir

Bien définir ses contraintes dès la création des tables permet de garantir la cohérence et la qualité des données dans la base. Une contrainte rejetée au niveau de la base vaut mieux qu'une erreur découverte dans l'application.

Modifier la structure d'une table : ALTER TABLE

Après la création, il est possible de modifier la structure d'une table sans la supprimer.

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+)
ALTER TABLE Etudiant RENAME COLUMN actif TO estActif;
À retenir

ALTER TABLE fait partie du DDL. L'utiliser quand le schéma doit évoluer sans perdre les données existantes. En production, ces modifications doivent être planifiées avec soin (risque de perte de données si on supprime une colonne).

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, ici actif, prennent leur valeur par défaut.

Le bloc n'affiche aucun tableau, et c'est normal : un INSERT modifie la table et ne rend aucune ligne. Pour voir le résultat, ajouter SELECT * FROM Etudiant WHERE nom = 'Durand'; sous l'insertion et relancer : Claire y apparaît avec l'identifiant 11 et actif à 1.

Insérer plusieurs lignes à la fois :

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

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);
À retenir

Il est toujours préférable de préciser les colonnes dans l'ordre d'insertion. Cela rend le code plus lisible, robuste et évite des erreurs en cas de changement dans la structure de la table.

Lecture des données : SELECT

Une requête s'écrit de haut en bas, SELECT sur la première ligne. Elle ne se construit pas dans cet ordre, et confondre les deux est la source d'erreur la plus fréquente au début.

L'ordre dans lequel une requête se construit
  1. FROM d'abord : dire d'où viennent les lignes. Sans table, rien d'autre n'a de sens.
  2. WHERE ensuite : dire lesquelles garder. À ce stade, toutes les colonnes de la table sont disponibles pour le filtre, y compris celles qui ne seront jamais affichées.
  3. SELECT en dernier : dire lesquelles afficher. C'est un choix d'affichage, pas un choix de données.

Commencer par SELECT mène à deux erreurs jumelles : filtrer sur une colonne qu'on a oublié de garder, ou croire qu'une colonne absente du SELECT est aussi absente du WHERE. Le moteur, lui, exécute dans ce même ordre de construction, ce qui a des conséquences visibles plus loin dans le chapitre.

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
=, !=, <, >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.

Le caractère joker % dans LIKE.

% remplace n'importe quelle suite de caractères (y compris vide).
_ remplace exactement un caractère.
Exemples : 'Du%' → commence par "Du" | '%on' → finit par "on" | 'D_pont' → "Dupont", "Damont", etc.

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.

Bonnes pratiques SELECT
  • Éviter SELECT * dans les applications : préférer lister les colonnes utiles.
  • Utiliser WHERE pour limiter le volume de données retourné.
  • Trier avec ORDER BY pour faciliter la lecture des résultats.
  • Utiliser LIMIT pour ne pas surcharger l'application lors des tests.

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 une ligne modifiée, rien de plus. Le seul moyen de vérifier une modification est de relire la table ensuite.

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.

Suppression : DELETE

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

Seul l'étudiant 4 est supprimé. 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 une ligne supprimée.

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

SQL
-- Supprime toutes les lignes une par une (lent, journalisé)
DELETE FROM Etudiant;
-- Vide la table d'un coup et réinitialise l'AUTO_INCREMENT (plus rapide, non journalisé)
TRUNCATE TABLE Etudiant;

DELETE vs TRUNCATE

DELETE sans WHERE supprime toutes les lignes mais conserve la structure et peut être annulé avec ROLLBACK.
TRUNCATE vide la table et remet l'AUTO_INCREMENT à 1. L'opération n'est pas annulable, et se réserve aux environnements de développement.

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.

Données d'exemple

Pour illustrer les jointures, imaginons ces deux tables :

Table Etudiant :

idEtudiantnomprenom
1MartinAlice
2DurandLucas
3LeroyEmma

Inscription (idCours = 10 = "Maths", 20 = "SQL")

idEtudiantidCours
110
120
210

Emma (id=3) n'a aucune inscription. Alice (id=1) en a deux. Ces deux particularités ne sont pas décoratives : ce sont elles qui font toute la différence entre les types de jointure.

La figure suivante prend ces deux tables et les joint des cinq façons possibles. L'appariement, lui, ne change jamais : ce qui change est le sort réservé aux lignes qui n'ont trouvé personne. Basculer d'un onglet à l'autre pour voir les orphelines apparaître et disparaître.

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.

Trois faits se lisent directement sur cette figure.

  • Alice se dédouble. Elle a deux inscriptions, elle apparaît donc deux fois. Une jointure peut rendre plus de lignes que la table de départ n'en contient, ce qui surprend systématiquement la première fois.
  • Emma disparaît en INNER, revient en LEFT. Elle n'a aucune inscription : la jointure interne l'écarte, la jointure externe gauche la garde en complétant sa droite par des NULL.
  • Ces NULL sont fabriqués. Aucune ligne de la base ne contient cette valeur. C'est la jointure qui l'invente pour combler la place d'une ligne qui n'existe pas, et c'est une source d'erreurs classique dans 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

Le bloc, lui, ne travaille pas sur ces trois lignes : il s'exécute sur la base de démonstration, qui compte dix étudiants et vingt-cinq inscriptions. Il rend donc vingt-cinq lignes et trois colonnes, une ligne par inscription appariée. Le principe est le même dans les deux cas.

Une JOIN (ou INNER JOIN) ne conserve que les lignes ayant une correspondance dans les deux tables. Emma n'apparaît pas car elle n'a aucune inscription.

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. Emma apparaît avec NULL dans idCours.

Exécuté sur la base de démonstration, ce bloc rend exactement les mêmes vingt-cinq lignes que le précédent. La raison compte : aucun étudiant de cette base n'est sans inscription, il n'y a donc aucune orpheline à rattraper. La différence entre les deux jointures ne se voit que lorsqu'une ligne reste seule, et c'est pour cela que la figure invente Emma.

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.

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, non supporté nativement par MySQL
CROSS JOINchaque ligne de gauche avec chaque ligne de droite, sans condition9 lignes, 3 × 3
À retenir
  • Utiliser INNER JOIN pour ne garder que les données qui ont une correspondance dans toutes les tables.
  • Utiliser LEFT JOIN pour garder tous les enregistrements de la table principale, même sans correspondance.
  • RIGHT JOIN est rarement utilisé : on préfère inverser l'ordre des tables et utiliser LEFT JOIN.

Requêtes avec agrégats

Une jointure rend une ligne par appariement, et ce compte devient vite trop gros pour être lu. La suite consiste à le réduire à un résumé.

Les fonctions d'agrégation font un calcul sur plusieurs lignes et rendent une seule valeur. Elles transforment une liste en statistique.

Fonctions d'agrégation disponibles

FonctionRôleExemple
COUNT()Compte le nombre de lignesCOUNT(*), COUNT(idEtudiant)
SUM()Somme les valeursSUM(montant)
AVG()Calcule la moyenneAVG(note)
MAX()Renvoie la valeur maximaleMAX(note)
MIN()Renvoie la valeur minimaleMIN(note)

COUNT(*) ou COUNT(colonne) ?

COUNT(*) compte toutes les lignes, y compris celles dont les colonnes sont à NULL.
COUNT(colonne) ne compte que les lignes où cette colonne n'est pas à NULL.

L'écart entre les deux comptes est donc le nombre de valeurs manquantes. C'est le moyen le plus rapide de savoir si une colonne est bien remplie.

Renommer une colonne avec AS

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

AS donne un alias lisible à la colonne résultante. C'est particulièrement utile avec les agrégats, qui n'ont pas de nom par défaut.

Le résultat contient une seule ligne et deux colonnes : 25 inscriptions, et une moyenne de 12,91. Deux pièges tiennent dans ces deux nombres. Le compte porte sur les lignes de Inscription, donc sur les inscriptions et non sur les étudiants, qui sont dix ; nommer cette colonne total_etudiants aurait fait mentir le résultat. Et la moyenne ne porte que sur 23 notes, parce que deux inscriptions n'en ont pas et que AVG ignore les NULL au lieu de les compter pour zéro.

Regrouper des résultats : GROUP BY

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

Le nombre d'inscrits et la moyenne sont maintenant calculés pour chaque cours. Le résultat contient sept lignes, une par cours de la base, et trois colonnes. GROUP BY ne filtre rien : il découpe les lignes en paquets, et chaque fonction d'agrégation travaille ensuite à l'intérieur d'un paquet.

Filtrer les groupes : HAVING

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

Le résultat contient cinq lignes au lieu de sept : deux cours sortent, leur moyenne ne dépassant pas 12. Ce sont bien des groupes qui ont été écartés, pas des lignes.

WHERE ou HAVING ?

WHERE filtre les lignes avant le regroupement : il s'applique aux données brutes.
HAVING filtre les groupes après le GROUP BY : il s'applique aux résultats agrégés.

WHERE AVG(note) > 12 n'existe pas, et la raison est la même que pour l'ordre de construction vu plus haut : au moment où WHERE s'applique, les groupes ne sont pas formés et la moyenne n'existe pas encore.

Exemple complet avec jointure + agrégat

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

Cette requête enchaîne les quatre gestes du chapitre :

  • Assembler Cours et Inscription sur l'identifiant de cours ;
  • Regrouper par intitulé, pour obtenir un paquet de lignes par cours ;
  • Écarter les groupes dont la moyenne est inférieure à 10 ;
  • Trier ce qui reste par moyenne décroissante.

Le résultat contient sept lignes et trois colonnes : sur cette base, les sept cours atteignent tous la moyenne de 10, et le HAVING n'écarte donc personne. Remplacer 10 par 13 et relancer : il reste quatre cours, Mathematiques en tête avec 14,4.

Ordre d'exécution d'une requête SQL

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 dans lequel le moteur travaille. Le voici en entier, les clauses vues depuis intercalées à leur place.

Ordre d'exécutionClauseRôle
1FROM + JOINCharge les tables et les assemble
2WHEREFiltre les lignes
3GROUP BYRegroupe les lignes restantes
4HAVINGFiltre les groupes
5SELECTSélectionne les colonnes (et applique les agrégats)
6DISTINCTSupprime les doublons
7ORDER BYTrie les résultats
8LIMITLimite le nombre de lignes retournées

Ce que ce tableau explique. Un alias défini dans SELECT ne peut pas servir dans WHERE : à l'étape 2, l'étape 5 n'a pas eu lieu et l'alias n'existe pas.
Le même alias fonctionne en revanche dans ORDER BY, qui vient après. Les deux règles ne s'apprennent pas séparément : elles se déduisent de la colonne de gauche.

La figure suivante fait tourner une requête à jointure et agrégat, étape par étape. Cliquer sur chaque clause montre la relation à ce moment précis.

SELECT nomClasse, COUNT(*) AS effectifFROM EtudiantJOIN Classe ON Etudiant.idClasse = Classe.idClasseWHERE actif = 1GROUP BY nomClasseHAVING COUNT(*) >= 3ORDER BY effectif DESC

Ordre d'exécution. Cliquer sur une étape pour voir ce qu'elle produit.

6. ORDER BYordonne le résultat, une fois qu'il existe2 lignes
nomClasseeffectif
1A3
2A3
La jointure a lieu dès l'étape 1, avec FROM. Le filtre sur actif vient ensuite, sur les lignes ; le filtre sur l'effectif vient bien plus tard, sur les groupes.

Suivre la clause HAVING est instructif : elle porte sur COUNT(*), une valeur qui n'existe qu'à partir de l'étape 3. L'écrire dans WHERE reviendrait à la demander à l'étape 2, avant qu'elle soit calculable, et le moteur refuse.

Créer une table avec une clé étrangère

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.
  • 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.

Ce que garantit l'intégrité référentielle. Sous MySQL, la seconde insertion ci-dessous est refusée avant même d'être écrite :

-- Accepté : l'étudiant 1 et le cours 10 existent tous les deux
INSERT INTO Inscription VALUES (1, 10, 14.5);

-- Refusé : l'étudiant 99 n'existe pas dans la table Etudiant
INSERT INTO Inscription VALUES (99, 10, 12.0);
-- Error: Cannot add or update a child row: a foreign key constraint fails

Ce bloc est en lecture seule, contrairement aux autres : le moteur embarqué dans cette page ne vérifie pas les clés étrangères, et laisserait passer les deux insertions sans rien dire. C'est justement ce que garantit un vrai SGBD et que la page ne peut pas montrer. Une contrainte non vérifiée par le moteur devient une contrainte que personne ne respecte.

À retenir

Les clés étrangères garantissent l'intégrité référentielle : on ne peut pas créer de liens vers des données inexistantes.
Choisir entre ON DELETE CASCADE (suppression en cascade) et ON DELETE RESTRICT (blocage) selon le comportement souhaité.

La méthode

  1. Écrire FROM en premier, avec les tables nécessaires et leurs alias. Une table qui n'apporte ni colonne affichée ni colonne filtrée est de trop.
  2. Poser chaque jointure avec son ON dès que FROM cite plus d'une table. Une jointure oubliée ne provoque aucune erreur : elle produit un produit cartésien, et le nombre de lignes explose sans prévenir.
  3. Écrire WHERE ensuite, en filtrant sur les colonnes brutes. Toutes les colonnes des tables sont disponibles ici, y compris celles qui ne seront pas affichées.
  4. Regrouper puis filtrer les groupes quand la question porte sur un résumé : GROUP BY sur les colonnes qui identifient un paquet, HAVING sur les fonctions d'agrégation, jamais l'inverse.
  5. Écrire SELECT en dernier, en nommant les colonnes une à une et en donnant un alias à chaque agrégat. Un alias qui décrit mal ce qui est compté fait mentir le résultat.
  6. Ajouter ORDER BY puis LIMIT en toute fin, pour la lecture seulement : ils ne changent jamais quelles lignes sont retenues, seulement lesquelles sont montrées.
  7. Compter le résultat attendu avant de lancer. Un résultat plus long que prévu signale une jointure qui dédouble ; un résultat vide signale un filtre trop strict ou un NULL oublié.
  8. Relire tout UPDATE et tout DELETE en cherchant son WHERE avant de l'exécuter. C'est le seul geste de cette liste qui protège des données.

Synthèse

  • SQL est un langage déclaratif : une requête décrit le résultat voulu, jamais la marche à suivre. C'est le SGBD qui choisit comment l'obtenir.
  • Les commandes se rangent en familles : le DDL définit les structures, le DML modifie les données, le DQL les interroge, le DCL gère les droits, le TCL les transactions. Au quotidien, le DML et le DQL font l'essentiel.
  • Créer une table consiste à nommer ses colonnes, leur donner un type et leur attacher des contraintes. PRIMARY KEY, NOT NULL, UNIQUE, CHECK et DEFAULT font refuser par la base ce qui serait découvert trop tard dans l'application.
  • Une clé étrangère relie une table à une autre et garantit l'intégrité référentielle : aucune ligne ne peut désigner une ligne inexistante. ON DELETE CASCADE propage la suppression, ON DELETE RESTRICT la bloque.
  • INSERT, UPDATE et DELETE ne rendent aucun tableau : ils modifient la table et annoncent un nombre de lignes touchées. Un UPDATE ou un DELETE sans WHERE s'applique à toute la table.
  • Une requête se construit dans l'ordre FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY, LIMIT, qui est aussi l'ordre dans lequel le moteur l'exécute. L'ordre d'écriture, lui, commence par SELECT : les confondre explique la plupart des erreurs de débutant.
  • Une jointure interne ne garde que les lignes appariées des deux côtés ; une jointure externe garde les orphelines d'un côté et fabrique des NULL pour l'autre. Ces NULL n'existent nulle part dans la base.
  • Une jointure peut rendre plus de lignes que la table de départ n'en contient, puisqu'une ligne de gauche appariée trois fois produit trois lignes. Tout comptage fait ensuite sur ce résultat porte sur les appariements, pas sur les lignes d'origine.
  • Les fonctions d'agrégation résument un paquet de lignes en une valeur. COUNT(*) compte les lignes, COUNT(colonne) seulement celles qui portent une valeur, et AVG, SUM, MIN, MAX ignorent les NULL au lieu de les compter pour zéro.
  • WHERE filtre des lignes avant le regroupement, HAVING filtre des groupes après. Écrire une fonction d'agrégation dans WHERE est refusé, parce qu'à cette étape les groupes n'existent pas encore.

Quiz

Vérification rapideon peut se reprendre

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

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

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

4.Pourquoi ne peut-on pas employer dans WHERE un alias défini dans SELECT ?

5.DELETE FROM Etudiant; sans clause WHERE fait quoi ?

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

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

Ressources complémentaires

Mettre en pratique