Vue d'ensemble
Une base de données relationnelle stocke l'information dans des tables (des tableaux à lignes et colonnes). Encore faut-il savoir la faire parler : combien d'élèves en MP ? Quelle est la moyenne de chacun ? Qui a plus de 13 de moyenne ? Toutes ces questions se posent dans un seul et même langage, SQL (Structured Query Language). Cette fiche te donne les huit briques du programme — SELECT, WHERE, DISTINCT, ORDER BY, les jointures, les fonctions d'agrégation, GROUP BY et HAVING — et surtout la logique d'assemblage qui te permet de lire ou d'écrire n'importe quelle requête sans paniquer le jour du concours.
Toute la fiche s'appuie sur une base minuscule et fixe, que tu dois avoir en tête. Deux tables :
| id_eleve | nom | classe |
|---|---|---|
| 1 | Alice | MP |
| 2 | Bob | MP |
| 3 | Chloé | PC |
| 4 | David | PC |
| id_note | id_eleve | matiere | valeur |
|---|---|---|---|
| 1 | 1 | Maths | 15 |
| 2 | 1 | Info | 18 |
| 3 | 2 | Maths | 12 |
| 4 | 2 | Info | 9 |
| 5 | 3 | Maths | 14 |
| 6 | 3 | Info | 16 |
| 7 | 4 | Maths | 8 |
| 8 | 1 | Physique | 11 |
La colonne id_eleve de Note renvoie à la colonne id_eleve de Eleve : c'est la clé étrangère qui relie une note à son élève. Remarque déjà qu'Alice (id 1) a trois notes, David (id 4) n'en a qu'une, et personne n'a de note nulle. Ces détails feront la différence dans les résultats.
SELECT ... FROM), sélection (WHERE avec AND, OR, comparaisons), DISTINCT, tri (ORDER BY), jointure de deux tables (JOIN ... ON), fonctions d'agrégation (COUNT, SUM, AVG, MIN, MAX), regroupement (GROUP BY) et filtrage des groupes (HAVING). Hors programme ici : sous-requêtes, INSERT/UPDATE/DELETE, jointures externes.
Prérequis
- Le modèle relationnel : notions de table (relation), ligne (n-uplet), colonne (attribut), clé primaire et clé étrangère.
- Savoir lire un schéma de table du type
Eleve(id_eleve, nom, classe). - Aucune notion de programmation Python n'est requise : SQL est un langage à part, déclaratif (on décrit CE qu'on veut, pas COMMENT le calculer).
SQL se joue à l'oral comme à l'écrit. Savoir dérouler une requête ligne par ligne devant un examinateur, c'est ce qui transforme une bonne note en excellente note. Nos mentors alumni X · Centrale · Mines t'entraînent sur des requêtes de concours jusqu'à ce que la logique devienne un réflexe.
Trouver un mentor →1. Projeter : SELECT ... FROM
Une requête est une phrase du langage SQL qui interroge la base et renvoie une table de résultat (elle aussi faite de lignes et de colonnes). La requête ne modifie pas la base : elle en extrait une vue. La brique de base est SELECT liste-de-colonnes FROM table, qui projette la table sur les colonnes demandées.
Demandons le nom et la classe de tous les élèves.
SELECT nom, classe
FROM Eleve;SELECT nom, classeon choisit les colonnes à afficher, dans l'ordre écrit : d'abord nom, puis classe. On IGNORE les autres colonnes (id_eleve). C'est la projection.FROM Eleveon précise DANS QUELLE table piocher ces colonnes : la table Eleve.;le point-virgule termine la requête (facultatif quand il n'y en a qu'une, obligatoire pour en enchaîner plusieurs).SQL parcourt les quatre lignes de Eleve et ne garde que les deux colonnes demandées :
| nom | classe |
|---|---|
| Alice | MP |
| Bob | MP |
| Chloé | PC |
| David | PC |
📝 SELECT * (l'étoile) veut dire « toutes les colonnes ». SELECT * FROM Eleve renverrait les trois colonnes id_eleve, nom, classe. Pratique pour explorer, mais en composition on projette explicitement ce dont on a besoin.
📝 On peut renommer une colonne de résultat avec AS : SELECT nom AS eleve FROM Eleve affiche la colonne sous l'en-tête eleve. Indispensable pour donner un nom lisible au résultat d'un calcul, par exemple AVG(valeur) AS moyenne.
2. Filtrer les lignes : WHERE
La clause WHERE ne garde que les lignes qui vérifient une condition. Cherchons les élèves de la classe MP.
SELECT nom
FROM Eleve
WHERE classe = 'MP';SELECT nomon ne veut afficher que le nom.FROM Elevedans la table Eleve.WHERE classe = 'MP'condition testée ligne par ligne : on garde une ligne SEULEMENT si sa colonne classe vaut exactement la chaîne 'MP'. En SQL, le test d'égalité s'écrit avec un seul = (pas ==), et une chaîne de caractères est entre apostrophes simples.| nom |
|---|
| Alice ✓ |
| Bob ✓ |
Combiner des conditions : AND, OR, comparaisons
On dispose des comparateurs =, <> (différent), <, <=, >, >=, et des connecteurs logiques AND (et) et OR (ou). Cherchons les notes de Maths supérieures ou égales à 12.
SELECT id_eleve, matiere, valeur
FROM Note
WHERE valeur >= 12 AND matiere = 'Maths';SELECT id_eleve, matiere, valeurtrois colonnes affichées.FROM Noteon travaille cette fois sur la table des notes.WHERE valeur >= 12 AND matiere = 'Maths'une ligne est gardée si les DEUX conditions sont vraies en même temps : la note vaut au moins 12 et la matière est Maths. AND exige les deux ; OR se contenterait d'une seule.Passons les huit notes au crible. Les notes de Maths sont 15, 12, 14, 8 ; on garde celles , donc 15, 12, 14 (le 8 de David est éliminé, et toutes les notes d'Info/Physique aussi car mauvaise matière).
| id_eleve | matiere | valeur |
|---|---|---|
| 1 | Maths | 15 ✓ |
| 2 | Maths | 12 ✓ |
| 3 | Maths | 14 ✓ |
⚠ Une chaîne va entre apostrophes simples ('Maths'), un nombre s'écrit nu (12, sans apostrophes). WHERE valeur = '12' peut donner un résultat inattendu selon le SGBD, car on compare alors un nombre à un texte. Et = est sensible à la casse du contenu : 'maths' ne vaut pas 'Maths'.
3. Dédoublonner et trier : DISTINCT, ORDER BY
DISTINCT — éliminer les doublons
La colonne matiere de Note contient des répétitions (Maths apparaît quatre fois). DISTINCT ne garde qu'un exemplaire de chaque valeur.
SELECT DISTINCT matiere
FROM Note;SELECT DISTINCT matiereDISTINCT se place juste après SELECT et supprime les lignes de résultat en double. On obtient la liste des matières SANS répétition.FROM Notesource : les huit notes.| matiere |
|---|
| Maths |
| Info |
| Physique |
ORDER BY — trier le résultat
ORDER BY colonne trie les lignes du résultat. Par défaut c'est croissant (ASC) ; DESC donne l'ordre décroissant. Trions les notes de la plus haute à la plus basse.
SELECT id_eleve, matiere, valeur
FROM Note
ORDER BY valeur DESC;SELECT id_eleve, matiere, valeurcolonnes affichées.FROM Noteles huit notes.ORDER BY valeur DESCon réordonne le résultat par valeur décroissante (DESC). Avec ASC (ou rien), ce serait croissant. ORDER BY ne change jamais QUELLES lignes sortent, seulement leur ORDRE d'affichage.| id_eleve | matiere | valeur |
|---|---|---|
| 1 | Info | 18 |
| 3 | Info | 16 |
| 1 | Maths | 15 |
| 3 | Maths | 14 |
| 2 | Maths | 12 |
| 1 | Physique | 11 |
| 2 | Info | 9 |
| 4 | Maths | 8 |
📝 On peut trier sur plusieurs colonnes : ORDER BY classe ASC, nom DESC trie d'abord par classe croissante, puis, à classe égale, par nom décroissant. La première colonne prime, la seconde départage les ex æquo.
4. Croiser deux tables : la jointure JOIN
Une jointure combine les lignes de deux tables en les appariant selon une condition de correspondance (typiquement l'égalité d'une clé étrangère et d'une clé primaire). FROM A JOIN B ON A.col = B.col produit, pour chaque paire de lignes qui vérifie la condition ON, une ligne unique réunissant les colonnes des deux tables.
La table Note ne stocke que l'id_eleve : impossible d'y lire le nom de l'élève. Pour afficher « Alice a 15 en Maths », il faut raccorder chaque note à sa ligne d'élève via l'égalité des identifiants.
SELECT Eleve.nom, Note.matiere, Note.valeur
FROM Eleve
JOIN Note ON Eleve.id_eleve = Note.id_eleve;SELECT Eleve.nom, Note.matiere, Note.valeuron affiche des colonnes venant des DEUX tables. On préfixe par le nom de la table (Eleve.nom, Note.valeur) pour lever toute ambiguïté, surtout quand une colonne existe des deux côtés (ici id_eleve).FROM Elevepremière table de la jointure.JOIN Note ON Eleve.id_eleve = Note.id_eleveon colle la table Note. La condition ON dit COMMENT apparier : on ne réunit une ligne de Eleve et une ligne de Note que si elles portent le MÊME id_eleve. Chaque note retrouve ainsi son élève.Chaque note (8 lignes) est reliée à l'unique élève qui la porte : on obtient donc 8 lignes, enrichies du nom. Comme David (id 4) n'a qu'une note, il n'apparaît qu'une fois ; Alice (id 1) apparaît trois fois.
| nom | matiere | valeur |
|---|---|---|
| Alice | Info | 18 |
| Alice | Maths | 15 |
| Alice | Physique | 11 |
| Bob | Info | 9 |
| Bob | Maths | 12 |
| Chloé | Info | 16 |
| Chloé | Maths | 14 |
| David | Maths | 8 |
📐 Réflexe jointure. Dès qu'une question mêle des informations de deux tables (« le NOM de l'élève » + « la VALEUR de sa note »), il faut une jointure. Repère la colonne partagée (ici id_eleve), écris la clause ON comme l'égalité de cette colonne dans les deux tables, puis ajoute au besoin un WHERE pour filtrer. Exemple : ne garder que les notes d'Info.
SELECT Eleve.nom, Note.valeur
FROM Eleve
JOIN Note ON Eleve.id_eleve = Note.id_eleve
WHERE Note.matiere = 'Info';SELECT Eleve.nom, Note.valeurnom de l'élève et valeur de sa note.FROM Eleve JOIN Note ON Eleve.id_eleve = Note.id_eleveon raccorde d'abord les deux tables comme précédemment.WHERE Note.matiere = 'Info'sur les 8 lignes jointes, on ne garde que celles dont la matière est Info. On précise Note.matiere car c'est une colonne de la table Note.| nom | valeur |
|---|---|
| Alice | 18 ✓ |
| Bob | 9 ✓ |
| Chloé | 16 ✓ |
La jointure est LE point qui fait décrocher. Tant que le mécanisme « une ligne par appariement valide » n'est pas limpide, GROUP BY et HAVING restent flous. Nos mentors alumni X · Centrale · Mines te font visualiser la table jointe avant d'agréger, pour que tout s'enchaîne naturellement.
Trouver un mentor →5. Résumer une colonne : les fonctions d'agrégation
Une fonction d'agrégation écrase toute une colonne en une seule valeur. Les cinq du programme :
COUNT: compte des lignes ;SUM: somme des valeurs ;AVG: moyenne (average) ;MIN: plus petite valeur ;MAX: plus grande valeur.
Combien de notes la base contient-elle, et quelle est leur moyenne ?
SELECT COUNT(*), AVG(valeur)
FROM Note;SELECT COUNT(*), AVG(valeur)COUNT(*) compte TOUTES les lignes de la table (ici 8). AVG(valeur) fait la moyenne de la colonne valeur. Sans GROUP BY, l'agrégat porte sur l'ensemble de la table : le résultat tient sur UNE seule ligne.FROM Noteon agrège les huit notes.Somme des valeurs : , sur 8 notes, donc moyenne .
| COUNT(*) | AVG(valeur) |
|---|---|
| 8 | 12.875 |
⚠ COUNT(*) vs COUNT(colonne). COUNT(*) compte toutes les lignes. COUNT(colonne) ne compte que les lignes où cette colonne n'est PAS nulle (NULL). Sur des données sans valeur manquante les deux coïncident, mais dès qu'une colonne contient des NULL, COUNT(colonne) renvoie moins que COUNT(*). De même, AVG, SUM, MIN, MAX ignorent les NULL.
On peut cumuler plusieurs agrégats : la note la plus basse, la plus haute et la somme totale.
SELECT MIN(valeur), MAX(valeur), SUM(valeur)
FROM Note;MIN(valeur)plus petite note de la colonne : 8.MAX(valeur)plus grande note : 18.SUM(valeur)somme de toutes les notes : 103.| MIN(valeur) | MAX(valeur) | SUM(valeur) |
|---|---|---|
| 8 | 18 | 103 |
6. Agréger par groupe : GROUP BY
Un agrégat sur toute la table, c'est bien ; mais on veut souvent un résultat par élève ou par matière. GROUP BY colonne découpe la table en paquets de lignes qui partagent la même valeur de cette colonne, puis applique l'agrégat à CHAQUE paquet. Calculons la moyenne de chaque élève.
SELECT id_eleve, AVG(valeur)
FROM Note
GROUP BY id_eleve;SELECT id_eleve, AVG(valeur)pour chaque groupe on affiche l'identifiant de l'élève ET la moyenne de ses notes. Règle d'or : dans un SELECT avec GROUP BY, chaque colonne affichée est SOIT une colonne de regroupement (id_eleve), SOIT un agrégat (AVG(...)).FROM Notesource des notes.GROUP BY id_eleveon regroupe les 8 lignes par id_eleve : un paquet pour l'élève 1 (3 notes), un pour le 2 (2 notes), un pour le 3 (2 notes), un pour le 4 (1 note). L'AVG est calculé DANS chaque paquet.Détail des moyennes : élève 1 → ; élève 2 → ; élève 3 → ; élève 4 → .
| id_eleve | AVG(valeur) |
|---|---|
| 1 | 14.666… |
| 2 | 10.5 |
| 3 | 15.0 |
| 4 | 8.0 |
En joignant d'abord à Eleve, on remplace l'identifiant par le nom, plus lisible :
SELECT Eleve.nom, AVG(Note.valeur) AS moyenne
FROM Eleve
JOIN Note ON Eleve.id_eleve = Note.id_eleve
GROUP BY Eleve.nom;SELECT Eleve.nom, AVG(Note.valeur) AS moyennele nom du groupe et la moyenne, renommée moyenne grâce à AS.FROM Eleve JOIN Note ON ...on croise d'abord les deux tables : chaque note connaît désormais le nom de son élève.GROUP BY Eleve.nomon regroupe la table jointe par nom d'élève ; l'AVG porte sur chaque paquet de notes d'un même élève.| nom | moyenne |
|---|---|
| Alice | 14.666… |
| Bob | 10.5 |
| Chloé | 15.0 |
| David | 8.0 |
💡 Regrouper par matière. SELECT matiere, COUNT(*) FROM Note GROUP BY matiere compte les notes de chaque matière : Info → 3, Maths → 4, Physique → 1. On change juste la colonne de regroupement pour changer le point de vue.
7. Filtrer les groupes : HAVING
WHERE filtre des LIGNES avant regroupement ; il ne sait pas parler d'un agrégat. Pour poser une condition sur un GROUPE (par exemple « moyenne »), on utilise HAVING, qui s'applique APRÈS le GROUP BY. Ne gardons que les élèves à 13 ou plus de moyenne.
SELECT id_eleve, AVG(valeur)
FROM Note
GROUP BY id_eleve
HAVING AVG(valeur) >= 13;SELECT id_eleve, AVG(valeur)identifiant du groupe et sa moyenne.FROM Noteles notes.GROUP BY id_eleveon forme un groupe par élève et on calcule sa moyenne.HAVING AVG(valeur) >= 13on ne conserve que les GROUPES dont la moyenne atteint 13. Ici l'élève 1 (≈14,67) et l'élève 3 (15) passent ; l'élève 2 (10,5) et l'élève 4 (8) sont éliminés. Impossible d'écrire cette condition dans un WHERE, car AVG n'existe qu'une fois les groupes formés.| id_eleve | AVG(valeur) |
|---|---|
| 1 ✓ | 14.666… |
| 3 ✓ | 15.0 |
⚠ WHERE vs HAVING — le piège classique. WHERE filtre les lignes AVANT l'agrégation (« quelles notes entrent dans le calcul ? »), HAVING filtre les groupes APRÈS (« quels résultats agrégés garde-t-on ? »). On peut combiner les deux dans une même requête : le WHERE écarte d'abord certaines notes, puis le GROUP BY regroupe ce qui reste, puis le HAVING écarte certains groupes. Ne mets jamais un agrégat dans WHERE, ni une condition ligne-à-ligne dans HAVING quand un WHERE suffirait.
Assembler tout : une requête complète
Élèves ayant au moins 2 notes , triés par moyenne décroissante :
SELECT Eleve.nom, AVG(Note.valeur) AS moyenne
FROM Eleve
JOIN Note ON Eleve.id_eleve = Note.id_eleve
WHERE Note.valeur >= 10
GROUP BY Eleve.nom
HAVING COUNT(*) >= 2
ORDER BY moyenne DESC;FROM Eleve JOIN Note ON ...on croise élèves et notes.WHERE Note.valeur >= 10on écarte d'abord les notes < 10 : disparaissent le 9 de Bob et le 8 de David.GROUP BY Eleve.nomon regroupe les notes SURVIVANTES par élève. Alice garde 3 notes (18, 15, 11), Bob n'en garde qu'1 (12), Chloé 2 (16, 14), David 0.HAVING COUNT(*) >= 2on ne garde que les élèves ayant au moins 2 notes restantes : Alice (3) et Chloé (2) passent ; Bob (1 seule) est éliminé.SELECT Eleve.nom, AVG(Note.valeur) AS moyennemoyenne calculée sur les notes survivantes : Alice , Chloé .ORDER BY moyenne DESCon affiche de la meilleure moyenne à la moins bonne : Chloé (15) avant Alice (14,67).| nom | moyenne |
|---|---|
| Chloé ✓ | 15.0 |
| Alice ✓ | 14.666… |
📐 L'ordre logique d'évaluation (à connaître par cœur). On ÉCRIT la requête dans l'ordre SELECT … FROM … WHERE … GROUP BY … HAVING … ORDER BY, mais SQL l'ÉVALUE dans un autre ordre :
FROM/JOIN: on construit la table de travail (croisement des tables).WHERE: on filtre les lignes.GROUP BY: on forme les groupes.HAVING: on filtre les groupes.SELECT: on choisit et calcule les colonnes affichées.ORDER BY: on trie le résultat final.
Comprendre cet ordre explique tout : pourquoi WHERE ne connaît pas les agrégats (ils n'existent qu'à l'étape 3-4), pourquoi HAVING les connaît, et pourquoi le tri arrive en dernier.
Une requête complète, c'est six étapes enchaînées sans faute. S'entraîner à les dérouler dans l'ordre d'évaluation, c'est la clé pour ne plus jamais confondre WHERE et HAVING. Nos mentors alumni X · Centrale · Mines te donnent la méthode pas à pas sur des sujets tombés aux concours.
Trouver un mentor →Exercices corrigés
Sur la même base, écris une requête qui affiche le nom des élèves de la classe PC, triés par ordre alphabétique décroissant. Donne aussi le résultat.
Voir la correction détaillée
On travaille sur la seule table Eleve : projection de nom, filtre WHERE classe = 'PC', tri ORDER BY nom DESC.
SELECT nom
FROM Eleve
WHERE classe = 'PC'
ORDER BY nom DESC;Les élèves en PC sont Chloé et David. En ordre décroissant (D avant C) :
| nom |
|---|
| David ✓ |
| Chloé ✓ |
Écris une requête qui donne, pour chaque matière, le NOMBRE de notes et la note MAXIMALE. Déroule le résultat.
Voir la correction détaillée
« Pour chaque matière » impose un GROUP BY matiere. On affiche la colonne de regroupement et deux agrégats, COUNT(*) et MAX(valeur).
SELECT matiere, COUNT(*), MAX(valeur)
FROM Note
GROUP BY matiere;Info : notes 18, 9, 16 → 3 notes, max 18. Maths : 15, 12, 14, 8 → 4 notes, max 15. Physique : 11 → 1 note, max 11.
| matiere | COUNT(*) | MAX(valeur) |
|---|---|---|
| Info | 3 | 18 ✓ |
| Maths | 4 | 15 ✓ |
| Physique | 1 | 11 ✓ |
On veut le nom des élèves qui ont obtenu STRICTEMENT plus de 13 dans au moins deux matières, avec le nombre de telles notes. Écris la requête et donne le résultat.
Voir la correction détaillée
On a besoin du nom (table Eleve) et des notes (table Note) : jointure. « Plus de 13 » est une condition ligne-à-ligne → WHERE Note.valeur > 13. « Dans au moins deux matières » est une condition sur le GROUPE → HAVING COUNT(*) >= 2.
SELECT Eleve.nom, COUNT(*) AS nb
FROM Eleve
JOIN Note ON Eleve.id_eleve = Note.id_eleve
WHERE Note.valeur > 13
GROUP BY Eleve.nom
HAVING COUNT(*) >= 2;Étape WHERE (notes > 13, strictement) : Alice garde 18 et 15 ; Chloé garde 14 et 16 ; Bob (12, 9) et David (8) n'ont aucune note > 13. Après GROUP BY : Alice a 2 notes, Chloé 2. Le HAVING COUNT(*) >= 2 retient les deux.
| nom | nb |
|---|---|
| Alice ✓ | 2 |
| Chloé ✓ | 2 |
Attention : la note 14 de Chloé passe car 14 > 13. Bob est exclu : son 12 est < 13 et il n'a pas d'autre note haute. Si l'énoncé disait , rien ne changerait ici (aucune note ne vaut exactement 13).
Récap final — Ce qu'il faut absolument retenir
SQL est un langage déclaratif : tu décris le résultat voulu, le moteur trouve comment. Maîtriser les huit briques et surtout leur ordre d'évaluation, c'est pouvoir lire ou écrire n'importe quelle requête de concours.
- Sais-tu projeter avec
SELECT colonnes FROM tableet renommer avecAS? - Sais-tu filtrer les lignes avec
WHERE, combiner parAND/ORet mettre les chaînes entre apostrophes ? - Sais-tu quand utiliser
DISTINCTet comment trier avecORDER BY … ASC/DESC? - Sais-tu écrire une jointure
FROM A JOIN B ON A.cle = B.cleet pourquoi elle produit une ligne par appariement valide ? - Sais-tu ce que renvoient
COUNT,SUM,AVG,MIN,MAX, et la différence entreCOUNT(*)etCOUNT(colonne)? - Sais-tu qu'un
GROUP BYproduit une ligne PAR groupe, et que leSELECTn'y contient que colonnes de regroupement ou agrégats ? - Sais-tu distinguer
WHERE(filtre les lignes, avant) deHAVING(filtre les groupes, après) ? - Sais-tu réciter l'ordre d'évaluation FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY ?