Module 3Manipuler les données
Chapitre 7 sur 9
Réaliser des requêtes SQL
Interroger la base : filtres, tris, fonctions, agrégats, GROUP BY, jointures, sous-requêtes et vues.
- ≈ 195 min
- 9 leçons
- 3 vidéos
- 3 exercices
Module 3Manipuler les données
Chapitre 7 sur 9
Interroger la base : filtres, tris, fonctions, agrégats, GROUP BY, jointures, sous-requêtes et vues.
Objectifs
Avec ce chapitre, tu sauras…
SELECT : colonnes, alias, DISTINCT, calculs ;WHERE : comparaisons, AND, OR, NOT, BETWEEN, IN, LIKE, IS NULL ;ORDER BY, LIMIT ;COUNT, SUM, AVG, MIN, MAX) ;GROUP BY et filtrer les groupes avec HAVING ;INNER JOIN, LEFT JOIN) ;Toutes les requêtes de ce chapitre utilisent la base du centre de formation dans l'état du script d'insertion du chapitre 6 (section 5), sans les exemples de UPDATE et DELETE. Si tu as modifié tes données, supprime la base et réexécute le script de création et d'insertion.
Leçon 1
SELECT colonnes -- 5. ce qu'on affiche
FROM tables -- 1. d'où viennent les données
WHERE condition -- 2. filtre les lignes
GROUP BY colonnes -- 3. regroupe les lignes
HAVING condition -- 4. filtre les groupes
ORDER BY colonnes -- 6. trie le résultat
LIMIT n; -- 7. limite le nombre de lignesLeçon 2
SELECT * FROM FORMATION; -- toutes les colonnes
SELECT titreForm, prixForm FROM FORMATION; -- certaines colonnes
SELECT titreForm AS Titre, prixForm AS 'Prix (DH)' FROM FORMATION; -- alias
SELECT titreForm, prixForm * 1.2 AS prixTTC FROM FORMATION; -- colonne calculée
SELECT DISTINCT nomVilleEtu FROM ETUDIANT; -- sans doublons : Casablanca, Fès, Rabat, TangerVidéo · À regarder
ENMySQL Database - Full Course
freeCodeCamp.org · YouTube
Leçon 3
| Opérateur | Rôle | Exemple |
|---|---|---|
=, <> (ou !=), <, >, <=, >= | comparer | prixForm >= 3000 |
AND, OR, NOT | combiner des conditions | dureeForm = 30 AND prixForm < 3000 |
BETWEEN a AND b | dans un intervalle (bornes incluses) | dateNaissEtu BETWEEN '2003-01-01' AND '2004-12-31' |
Leçon 4
Leçon 5
| Famille | Fonctions MySQL | Exemple | Résultat |
|---|---|---|---|
| chaînes | CONCAT, UPPER, LOWER, LENGTH, SUBSTRING, TRIM, REPLACE | CONCAT(prenomEtu, ' ', UPPER(nomEtu)) | Sara BENALI |
| nombres | ROUND, FLOOR, CEIL, ABS, MOD | ROUND(3333.333, 2) | 3333.33 |
| dates |
Leçon 6
| Fonction | Calcule… |
|---|---|
COUNT(*) | le nombre de lignes |
COUNT(colonne) | le nombre de valeurs non NULL de la colonne |
SUM(colonne) | la somme |
AVG(colonne) | la moyenne |
MIN(colonne), MAX(colonne) | le minimum, le maximum |
Leçon 7
| nomEtu | prenomEtu | nomSess | typeCours |
|---|---|---|---|
| Benali | Sara | Dev intro | presentiel |
| Benali | Sara | Web été | distanciel |
| Tazi | Youssef | BD printemps | presentiel |
Leçon 8
| Écriture | La sous-requête renvoie… |
|---|---|
= (SELECT …), > (SELECT …) | une seule valeur (sinon erreur) |
IN (SELECT …), NOT IN (SELECT …) | une liste de valeurs (une colonne) |
EXISTS (SELECT …) | au moins une ligne ? (vrai / faux) |
Leçon 9
1Pour éliminer les doublons d'un résultat :
2LIKE 'B%' trouve les valeurs qui…
3Pour tester une valeur absente :
4Une condition sur COUNT(*) se place dans…
5Pour garder les sessions sans inscrit dans une jointure :
6ORDER BY prixForm DESC LIMIT 1 renvoie…
7Une vue…
L'essentiel du chapitre
SELECT … FROM … WHERE … GROUP BY … HAVING … ORDER BY … LIMIT.SELECT DISTINCT, alias AS, colonnes calculées ; éviter SELECT * dans le code.WHERE : = <> < >, AND OR NOT (parenthèses !), BETWEEN, IN, LIKE '%…_', IS NULL.CONCAT, UPPER, ROUND, CURDATE, DATEDIFF, TIMESTAMPDIFF, DATE_FORMAT, CASE, IFNULL.COUNT, SUM, AVG, MIN, MAX ; GROUP BY ; condition sur un agrégat → HAVING.INNER JOIN (correspondances), LEFT JOIN (toutes les lignes de gauche, NULL sinon).=), une liste (IN), une existence (EXISTS). Vue : CREATE VIEW nom AS SELECT ….IN (…)| dans une liste |
nomVilleEtu IN ('Rabat', 'Fès') |
LIKE | correspond à un motif : % = n'importe quelle suite, _ = un caractère | titreForm LIKE '%dév%' |
IS NULL, IS NOT NULL | valeur absente ou présente | adresseEtu IS NULL |
NOW(), CURDATE(), YEAR, MONTH, DAY, DATEDIFF, DATE_ADD, DATE_FORMAT, TIMESTAMPDIFFDATEDIFF(dateFinSess, dateDebutSess) |
| le nombre de jours |
| conditions | IF(cond, a, b), CASE WHEN … THEN … ELSE … END, IFNULL(x, défaut) | IFNULL(adresseEtu, 'non renseignée') |
| nomVilleEtu | nb |
|---|---|
| Casablanca | 2 |
| Rabat | 2 |
| Fès | 1 |
| Tanger | 1 |
| codeSess | titreForm | nbInscrits |
|---|---|---|
| S01 | Base de données | 3 |
| S02 | Base de données | 0 |
| S03 | Introduction au développement | 1 |
| S04 | C/C++ | 1 |
| S05 | Développement web | 3 |
Vidéo · À regarder
ENMySQL Database - Full Course
freeCodeCamp.org · YouTube
Vidéo · À regarder
ENPHP Full Course 2025 - Learn PHP from Scratch
Dani Krossing · YouTube
Lire des données avec SELECT (en anglais).
SELECT titreForm, prixForm FROM FORMATION ORDER BY prixForm DESC; -- du plus cher au moins cher
SELECT nomEtu, prenomEtu FROM ETUDIANT ORDER BY nomVilleEtu, nomEtu; -- par ville, puis par nom
SELECT titreForm, prixForm FROM FORMATION ORDER BY prixForm DESC LIMIT 3; -- les 3 plus chères
SELECT nomEtu FROM ETUDIANT ORDER BY nomEtu LIMIT 2 OFFSET 2; -- la « page 2 » de 2 lignesASC (croissant) est l'ordre par défaut ; DESC trie en ordre décroissant.
Tableau: Les fonctions d'agrégation
SELECT COUNT(*) AS nb, AVG(prixForm) AS moyenne, MIN(prixForm) AS mini, MAX(prixForm) AS maxi
FROM FORMATION;
-- 5 | 3100.00 | 2500.00 | 4000.00GROUP BY
GROUP BY regroupe les lignes qui ont la même valeur, puis calcule un agrégat par groupe.
SELECT nomVilleEtu, COUNT(*) AS nb
FROM ETUDIANT
GROUP BY nomVilleEtu
ORDER BY nb DESC;Les informations sont réparties dans plusieurs tables (c'est le but de la normalisation). Pour les réunir, on joint les tables sur leurs clés : la clé étrangère d'une table avec la clé primaire de l'autre.
INNER JOIN
SELECT e.nomEtu, e.prenomEtu, s.nomSess, i.typeCours
FROM ETUDIANT e
INNER JOIN INSCRIPTION i ON i.numCINEtu = e.numCINEtu
INNER JOIN SESSION s ON s.codeSess = i.codeSess
WHERE e.nomVilleEtu = 'Casablanca'
ORDER BY e.nomEtu;Une sous-requête est une requête placée à l'intérieur d'une autre, entre parenthèses.
-- les formations plus chères que la moyenne (3100)
SELECT codeForm, titreForm, prixForm
FROM FORMATION
WHERE prixForm > (SELECT AVG(prixForm) FROM FORMATION);
-- WEB01 Développement web 4000, PY01 Python 3500
-- les formations qui n'ont aucune session
SELECT codeForm, titreForm
FROM FORMATION
WHERE codeForm NOT IN (SELECT codeForm FROM SESSION);
-- PY01 Python
-- les étudiants qui ont au moins une inscription à distance
SELECT nomEtu, prenomEtu
FROM ETUDIANT e
WHERE EXISTS (SELECT 1 FROM INSCRIPTION i
WHERE i.numCINEtu = e.numCINEtu AND i.typeCours = 'distanciel');Une vue est une requête enregistrée sous un nom, qu'on interroge ensuite comme une table. Elle ne stocke pas de données : elle est recalculée à chaque utilisation. Elle sert à simplifier des requêtes complexes et à limiter ce que voit un utilisateur (on lui donne accès à la vue, pas aux tables).
CREATE VIEW v_ca_formation AS
SELECT f.codeForm, f.titreForm, COUNT(*) AS nbInscrits, SUM(f.prixForm) AS ca
FROM INSCRIPTION i
INNER JOIN SESSION s ON s.codeSess = i.codeSess
INNER JOIN FORMATION f ON f.codeForm = s.codeForm
GROUP BY f.codeForm, f.titreForm;
SELECT * FROM v_ca_formation ORDER BY ca DESC;
-- WEB01 3 inscrits 12000 ; BD001 3 inscrits 7500 ; ID01 2500 ; CCP01 3000
DROP VIEW v_ca_formation;Sur ta base bibliotheque (TP 6.4), écris et teste dix requêtes : les livres d'un genre donné triés par année ; les livres avec le nom de leurs auteurs ; le nombre d'exemplaires par livre ; les prêts en cours (non rendus) avec le nom de l'adhérent et le titre ; les prêts en retard (date prévue dépassée et non rendus) ; le nombre de prêts par adhérent, y compris 0 ; l'adhérent qui a le plus emprunté ; les livres jamais empruntés ; le nombre moyen de pages par genre ; une vue v_prets_en_cours.
Sur les données du chapitre 6, donne le résultat de chaque requête.
SELECT COUNT(*) FROM INSCRIPTION WHERE codeSess = 'S05';
SELECT COUNT(DISTINCT numCINEtu) FROM INSCRIPTION;
SELECT MAX(dureeForm) FROM FORMATION WHERE prixForm < 3000;
SELECT nomSpec, COUNT(c.codeForm) FROM SPECIALITE sp
LEFT JOIN COMBINAISON c ON c.codeSpec = sp.codeSpec GROUP BY sp.codeSpec, nomSpec;Écris une requête pour chaque question.
SELECT nomEtu, prenomEtu, dateNaissEtu
FROM ETUDIANT
WHERE dateNaissEtu BETWEEN '2003-01-01' AND '2004-12-31';
-- Saidi Imane, Benali Sara, Hilali Nour
SELECT titreForm FROM FORMATION WHERE titreForm LIKE '%dév%';
-- Introduction au développement, Développement web (la collation ignore la casse et les accents)
SELECT codeSess, nomSess FROM SESSION WHERE dateDebutSess >= '2026-06-01';
-- S02 BD automne, S05 Web étéWHERE adresseEtu = NULL ne renvoie aucune ligne, même si des adresses sont vides : NULL signifie « inconnu », et une comparaison avec l'inconnu n'est jamais vraie. Il faut écrire IS NULL.
WHERE nomVilleEtu = 'Rabat' OR nomVilleEtu = 'Fès' AND niveauEtu = 'Bac' se lit Rabat OR (Fès AND Bac) : tous les étudiants de Rabat, quel que soit leur niveau. Mets des parenthèses : WHERE (nomVilleEtu = 'Rabat' OR nomVilleEtu = 'Fès') AND niveauEtu = 'Bac', ou utilise IN.
SELECT CONCAT(prenomEtu, ' ', UPPER(nomEtu)) AS etudiant,
TIMESTAMPDIFF(YEAR, dateNaissEtu, CURDATE()) AS age
FROM ETUDIANT;
SELECT nomSess,
DATEDIFF(dateFinSess, dateDebutSess) AS nbJours,
DATE_FORMAT(dateDebutSess, '%d/%m/%Y') AS debut
FROM SESSION;
SELECT titreForm,
CASE
WHEN prixForm < 3000 THEN 'abordable'
WHEN prixForm < 4000 THEN 'moyen'
ELSE 'premium'
END AS gamme
FROM FORMATION;SELECT typeCours, COUNT(*) AS nb FROM INSCRIPTION GROUP BY typeCours;
-- distanciel 3, presentiel 5Dans une requête avec GROUP BY, chaque colonne du SELECT doit être soit dans le GROUP BY, soit dans une fonction d'agrégation. SELECT nomVilleEtu, nomEtu, COUNT(*) FROM ETUDIANT GROUP BY nomVilleEtu n'a pas de sens (quel nom afficher pour Casablanca ?) : MySQL 8 la refuse par défaut.
HAVING
WHERE filtre les lignes avant le regroupement ; HAVING filtre les groupes après, et peut donc utiliser les agrégats.
SELECT numCINEtu, COUNT(*) AS nbSessions
FROM INSCRIPTION
GROUP BY numCINEtu
HAVING COUNT(*) >= 2;
-- G683909, AB123456, BE998877 : 2 sessions chacunWHERE COUNT(*) >= 2 provoque une erreur : au moment du WHERE, les groupes n'existent pas encore. Une condition sur un agrégat va toujours dans le HAVING.
ETUDIANT e donne l'alias e à la table : on écrit e.nomEtu au lieu de ETUDIANT.nomEtu. Le préfixe est obligatoire quand une colonne existe dans plusieurs tables (numCINEtu est dans ETUDIANT et dans INSCRIPTION).
LEFT JOIN
INNER JOIN ne garde que les lignes qui ont une correspondance dans les deux tables. LEFT JOIN garde toutes les lignes de la table de gauche, avec des NULL quand il n'y a pas de correspondance.
SELECT s.codeSess, f.titreForm, COUNT(i.numCINEtu) AS nbInscrits
FROM SESSION s
INNER JOIN FORMATION f ON f.codeForm = s.codeForm
LEFT JOIN INSCRIPTION i ON i.codeSess = s.codeSess
GROUP BY s.codeSess, f.titreForm
ORDER BY s.codeSess;Avec un INNER JOIN vers INSCRIPTION, la session S02 (sans inscrit) aurait disparu du résultat. On compte COUNT(i.numCINEtu) et non COUNT(*) : pour S02, la seule ligne contient un NULL, qui n'est pas compté.
SELECT e.nomEtu, e.prenomEtu
FROM ETUDIANT e
LEFT JOIN INSCRIPTION i ON i.numCINEtu = e.numCINEtu
WHERE i.codeSess IS NULL;
-- Saidi Imane INNER JOIN : seulement les lignes communes A ∩ B
LEFT JOIN : toutes les lignes de A (+ B si existe) A
RIGHT JOIN : toutes les lignes de B (+ A si existe) BLes jointures SQL (en anglais).
Récupérer des données d'une base avec SELECT (en anglais).