Chapitre 516 minDébutant
La requête SELECT : WHERE, LIKE, ORDER BY
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- choisir les colonnes et filtrer les lignes avec
WHERE; - chercher avec
BETWEEN,INetLIKE; - traiter correctement les valeurs
NULL; - trier et paginer un résultat avec
ORDER BYetLIMIT.
5 exercices corrigés1 schéma
La requête SELECT lit les données d'une table : elle choisit les colonnes à afficher, garde les lignes qui vérifient la condition du WHERE et trie le résultat avec ORDER BY. Ce chapitre couvre aussi LIKE, IN, BETWEEN, le traitement de NULL, LIMIT et l'ordre dans lequel le SGBD évalue une requête. Les exemples interrogent la base d'exemple parc, dans son état initial.
Projection, expressions et alias
SELECT choisit les colonnes, FROM la table. L'étoile prend toutes les colonnes, dans l'ordre de la table :
SELECT * FROM service;
Dans une requête que tu gardes (une application, une vue, une copie d'examen), nomme les colonnes : le résultat ne change pas quand quelqu'un ajoute une colonne à la table, et le lecteur sait ce que tu affiches. Le choix des colonnes s'appelle la projection.
Une colonne du résultat peut être une expression calculée. Le mot-clé AS lui donne un alias, le nom qui apparaît dans l'en-tête :
SELECT num_inventaire, marque, modele, prix_achat,
ROUND(prix_achat * 1.2, 2) AS prix_ttc
FROM poste
WHERE type_poste = 'portable'
ORDER BY prix_achat DESC, num_inventaire;
| num_inventaire | marque | modele | prix_achat | prix_ttc |
|---|---|---|---|---|
| PC-0012 | Lenovo | ThinkPad T14 | 1249.00 | 1498.80 |
| PC-0014 | Lenovo | ThinkPad T14 | 1249.00 | 1498.80 |
| PC-0006 | Lenovo | ThinkPad T14 | 1189.00 | 1426.80 |
| PC-0002 | Lenovo | ThinkPad T14 | 1149.00 | 1378.80 |
| PC-0010 | Dell | Latitude 5440 | 1099.00 | 1318.80 |
| PC-0011 | Dell | Latitude 5440 | 1099.00 | 1318.80 |
| PC-0004 | HP | EliteBook 840 | 1029.00 | 1234.80 |
| PC-0005 | HP | EliteBook 840 | 1029.00 | 1234.80 |
Le AS est facultatif (ROUND(...) prix_ttc), mais il rend la requête plus lisible. Choisis des alias sans espace ni accent : prix_ttc plutôt que prix TTC, qui t'obligerait à écrire des backquotes partout.
DISTINCT
DISTINCT supprime les lignes en double du résultat :
SELECT DISTINCT marque FROM poste ORDER BY marque;
| marque |
|---|
| Dell |
| HP |
| Lenovo |
DISTINCT porte sur la ligne entière, pas sur la première colonne. SELECT DISTINCT marque, type_poste renvoie chaque combinaison différente : Dell fixe, Dell portable, HP fixe, HP portable, Lenovo portable.
WHERE et les opérateurs logiques
WHERE garde les lignes pour lesquelles la condition est vraie. Les comparaisons s'écrivent =, <> (ou !=), <, <=, >, >=. Les conditions se combinent avec AND, OR et NOT.
L'ordre d'évaluation est NOT, puis AND, puis OR, comme la multiplication passe avant l'addition. Tu veux les tickets ouverts ou en cours, de priorité critique :
SELECT id_ticket, statut, priorite
FROM ticket
WHERE statut = 'ouvert' OR statut = 'en_cours' AND priorite = 'critique'
ORDER BY id_ticket;
| id_ticket | statut | priorite |
|---|---|---|
| 7 | ouvert | normale |
| 12 | ouvert | basse |
| 13 | en_cours | critique |
| 14 | ouvert | normale |
Le AND est évalué d'abord : MySQL a compris ouvert OR (en_cours AND critique). Les tickets 7, 12 et 14 ne sont pas critiques. Avec des parenthèses :
SELECT id_ticket, statut, priorite
FROM ticket
WHERE (statut = 'ouvert' OR statut = 'en_cours') AND priorite = 'critique';
| id_ticket | statut | priorite |
|---|---|---|
| 13 | en_cours | critique |
BETWEEN, IN, LIKE et REGEXP
BETWEEN teste un intervalle, bornes comprises : prix_achat BETWEEN 600 AND 1100 équivaut à prix_achat >= 600 AND prix_achat <= 1100.
IN teste l'appartenance à une liste : priorite IN ('haute', 'critique') équivaut à deux = reliés par OR.
LIKE compare à un motif. % remplace n'importe quelle suite de caractères (y compris aucune), _ remplace exactement un caractère.
SELECT id_ticket, titre FROM ticket WHERE titre LIKE '%Odoo%';
| id_ticket | titre |
|---|---|
| 3 | Mot de passe Odoo oublié |
| 5 | Odoo très lent |
num_inventaire LIKE 'PC-001_' trouve les postes PC-0010 à PC-0015. Pour chercher un vrai % ou un vrai _, échappe-le : LIKE '%\_%'. Avec l'interclassement par défaut, LIKE ignore la casse et les accents, comme =.
REGEXP (ou REGEXP_LIKE()) compare à une expression régulière, avec la bibliothèque ICU depuis MySQL 8.0.4 :
SELECT num_inventaire, modele
FROM poste
WHERE modele REGEXP '^(OptiPlex|ProDesk)'
ORDER BY num_inventaire;
Elle renvoie les 7 postes fixes, dont les modèles commencent par OptiPlex ou ProDesk.
NULL et la logique à trois valeurs
NULL signifie « inconnu » ou « absent ». Ce n'est pas une valeur : toute comparaison avec NULL donne NULL, que SQL traite comme une troisième valeur de vérité, UNKNOWN (inconnu). WHERE ne garde que les lignes dont la condition vaut TRUE.
SELECT nom, prenom FROM employe WHERE telephone = NULL; -- aucune ligne, jamais
SELECT nom, prenom FROM employe WHERE telephone IS NULL; -- la bonne écriture
| nom | prenom |
|---|---|
| Petit | Thomas |
| Bonnet | Inès |
| Martin | Paul |
Les tables de vérité avec UNKNOWN :
| A | B | A AND B | A OR B | NOT A |
|---|---|---|---|---|
| TRUE | UNKNOWN | UNKNOWN | TRUE | FALSE |
| FALSE | UNKNOWN | FALSE | UNKNOWN | TRUE |
| UNKNOWN | UNKNOWN | UNKNOWN | UNKNOWN | UNKNOWN |
Conséquence moins visible : une condition négative ignore aussi les NULL.
SELECT libelle, etage FROM service WHERE etage <> 2;
| libelle | etage |
|---|---|
| Direction | 3 |
| Commercial | 1 |
| Logistique | 0 |
Juridique n'apparaît pas : NULL <> 2 vaut UNKNOWN. Si tu veux aussi les services sans étage, écris WHERE etage <> 2 OR etage IS NULL.
Toute opération avec NULL donne NULL : prix_achat + NULL vaut NULL, CONCAT('a', NULL) aussi. MySQL propose l'opérateur <=>, une égalité qui considère deux NULL comme égaux ; la norme et PostgreSQL écrivent IS NOT DISTINCT FROM.
ORDER BY
ORDER BY trie le résultat : ASC (croissant, par défaut) ou DESC. Plusieurs critères se départagent dans l'ordre où tu les écris. Tu peux trier sur une colonne, un alias du SELECT ou une expression.
SELECT libelle, etage FROM service ORDER BY etage, libelle;
| libelle | etage |
|---|---|
| Juridique | NULL |
| Logistique | 0 |
| Commercial | 1 |
| Comptabilité | 2 |
| Informatique | 2 |
| Direction | 3 |
MySQL place les NULL en premier dans un tri croissant et en dernier dans un tri décroissant. PostgreSQL fait l'inverse et propose NULLS FIRST ou NULLS LAST, que MySQL ne connaît pas. Pour envoyer les NULL à la fin avec MySQL, trie d'abord sur etage IS NULL, qui vaut 0 ou 1 : ORDER BY etage IS NULL, etage.
Sans ORDER BY, l'ordre des lignes n'est pas garanti. Il dépend du plan d'exécution et peut changer d'une version à l'autre.
LIMIT, OFFSET et pagination
LIMIT n garde les n premières lignes du résultat trié. Les trois postes les plus chers :
SELECT num_inventaire, prix_achat
FROM poste
ORDER BY prix_achat DESC, num_inventaire
LIMIT 3;
| num_inventaire | prix_achat |
|---|---|
| PC-0012 | 1249.00 |
| PC-0014 | 1249.00 |
| PC-0006 | 1189.00 |
Le second critère de tri (num_inventaire) départage les deux postes à 1 249 €. Sans lui, MySQL pourrait renvoyer PC-0014 avant PC-0012, et un autre jour l'inverse.
OFFSET saute des lignes. Pour afficher une liste page par page, avec t lignes par page, la page p s'obtient avec LIMIT t OFFSET (p - 1) * t :
-- Page 2 des tickets, du plus récent au plus ancien, 5 par page
SELECT id_ticket, titre, date_ouverture
FROM ticket
ORDER BY date_ouverture DESC
LIMIT 5 OFFSET 5;
| id_ticket | titre | date_ouverture |
|---|---|---|
| 9 | Rançongiciel suspecté | 2026-10-05 08:05:00 |
| 8 | Pièce jointe trop lourde | 2026-10-01 11:12:00 |
| 7 | Installer Sage pour Paul Martin | 2026-09-23 09:00:00 |
| 6 | Ventilateur bruyant | 2026-09-22 15:40:00 |
| 5 | Odoo très lent | 2026-09-15 10:20:00 |
MySQL accepte aussi LIMIT 5, 5 (décalage, puis nombre) : l'ordre des deux nombres y est inversé, source d'erreurs. La norme SQL écrit OFFSET 5 ROWS FETCH FIRST 5 ROWS ONLY, forme que PostgreSQL et SQL Server comprennent ; SQL Server a aussi SELECT TOP (5).
Ordre d'écriture, ordre d'exécution
Tu écris une requête dans un ordre imposé par la syntaxe. Le SGBD l'évalue, logiquement, dans un autre ordre :
Cet ordre explique des erreurs qui semblent arbitraires :
SELECT num_inventaire, ROUND(prix_achat * 1.2, 2) AS prix_ttc
FROM poste
WHERE prix_ttc > 1300;
ERROR 1054 (42S22): Unknown column 'prix_ttc' in 'where clause'
Quand WHERE s'exécute, SELECT n'a pas encore calculé prix_ttc. Répète l'expression dans le WHERE (WHERE prix_achat * 1.2 > 1300) ou passe par une sous-requête. En revanche, ORDER BY prix_ttc fonctionne : le tri vient après le SELECT.
L'optimiseur peut exécuter physiquement les opérations dans un autre ordre (filtrer avant de joindre, utiliser un index pour éviter un tri), mais le résultat est toujours celui de l'ordre logique.
Exercices
Exercice 1 sur 5 Les embauches récentes Réussi À revoir
Affiche le nom, le prénom et la date d'embauche des employés embauchés de 2020 à 2023 inclus, du plus ancien au plus récent.
Voir la réponseMasquer la réponse de l'exercice 1
SELECT nom, prenom, date_embauche
FROM employe
WHERE date_embauche >= '2020-01-01' AND date_embauche < '2024-01-01'
ORDER BY date_embauche;
| nom | prenom | date_embauche |
|---|---|---|
| Lambert | Chloé | 2020-09-01 |
| Roux | Camille | 2021-05-17 |
| Bonnet | Inès | 2022-10-03 |
| Diallo | Moussa | 2023-09-04 |
BETWEEN '2020-01-01' AND '2023-12-31' donne le même résultat ici, car date_embauche est une DATE. La forme avec < reste juste si la colonne devient un jour un DATETIME.
Tu avais trouvé ?
Exercice 2 sur 5 Les urgences en attente Réussi À revoir
Affiche les tickets non résolus de priorité haute ou critique : numéro, titre, priorité et statut.
Voir la réponseMasquer la réponse de l'exercice 2
SELECT id_ticket, titre, priorite, statut
FROM ticket
WHERE statut <> 'resolu'
AND priorite IN ('haute', 'critique')
ORDER BY id_ticket;
| id_ticket | titre | priorite | statut |
|---|---|---|---|
| 11 | Accès VPN impossible | haute | en_cours |
| 13 | Lenteurs réseau à l'entrepôt | critique | en_cours |
IN évite le mélange de AND et OR. Avec OR, il faudrait des parenthèses : AND (priorite = 'haute' OR priorite = 'critique').
Tu avais trouvé ?
Exercice 3 sur 5 Demandes sans poste Réussi À revoir
Affiche le numéro et le titre des tickets qui ne concernent aucun poste. Explique pourquoi WHERE id_poste = NULL ne fonctionne pas.
Voir la réponseMasquer la réponse de l'exercice 3
SELECT id_ticket, titre
FROM ticket
WHERE id_poste IS NULL
ORDER BY id_ticket;
| id_ticket | titre |
|---|---|
| 2 | Imprimante du 2e étage hors ligne |
| 4 | Créer le compte de Paul Martin |
| 7 | Installer Sage pour Paul Martin |
| 13 | Lenteurs réseau à l'entrepôt |
id_poste = NULL vaut UNKNOWN pour toutes les lignes, y compris celles où id_poste est NULL : WHERE n'en garde aucune.
Tu avais trouvé ?
Exercice 4 sur 5 Pagination Réussi À revoir
Une page de l'outil de suivi affiche les postes du moins cher au plus cher, 4 par page, à prix égal par numéro d'inventaire. Écris la requête de la page 3. Quels postes y apparaissent ?
Voir la réponseMasquer la réponse de l'exercice 4
SELECT num_inventaire, prix_achat
FROM poste
ORDER BY prix_achat, num_inventaire
LIMIT 4 OFFSET 8;
| num_inventaire | prix_achat |
|---|---|
| PC-0005 | 1029.00 |
| PC-0010 | 1099.00 |
| PC-0011 | 1099.00 |
| PC-0002 | 1149.00 |
Page 3 : on saute (3 − 1) × 4 = 8 lignes. La page 1 contient les trois postes à 579 € et celui à 619 € ; la page 2, les trois postes à 689 € et PC-0004 à 1 029 €. Son jumeau PC-0005 ouvre la page 3 : sans le second critère de tri, l'un des deux pourrait apparaître sur les deux pages, ou sur aucune.
Tu avais trouvé ?
Exercice 5 sur 5 Trouve l'erreur Réussi À revoir
Cette requête doit afficher les postes achetés depuis plus de 5 ans au 28 octobre 2026, avec leur âge en jours. Elle renvoie une erreur. Laquelle, et comment la corriger ?
SELECT num_inventaire, DATEDIFF('2026-10-28', date_achat) AS age_jours
FROM poste
WHERE age_jours > 5 * 365
ORDER BY age_jours DESC;
Voir la réponseMasquer la réponse de l'exercice 5
Erreur 1054, Unknown column 'age_jours' in 'where clause' : l'alias n'existe pas encore quand WHERE est évalué. Répète l'expression dans le WHERE ; l'alias reste utilisable dans ORDER BY.
SELECT num_inventaire, DATEDIFF('2026-10-28', date_achat) AS age_jours
FROM poste
WHERE DATEDIFF('2026-10-28', date_achat) > 5 * 365
ORDER BY age_jours DESC, num_inventaire;
Le chapitre Index SQL et EXPLAIN explique pourquoi WHERE date_achat < '2021-10-28' est encore meilleur : sans fonction appliquée à la colonne, MySQL peut utiliser un index.
Tu avais trouvé ?