Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

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, IN et LIKE ;
  • traiter correctement les valeurs NULL ;
  • trier et paginer un résultat avec ORDER BY et LIMIT.

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 :

sql
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 :

sql
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_inventairemarquemodeleprix_achatprix_ttc
PC-0012LenovoThinkPad T141249.001498.80
PC-0014LenovoThinkPad T141249.001498.80
PC-0006LenovoThinkPad T141189.001426.80
PC-0002LenovoThinkPad T141149.001378.80
PC-0010DellLatitude 54401099.001318.80
PC-0011DellLatitude 54401099.001318.80
PC-0004HPEliteBook 8401029.001234.80
PC-0005HPEliteBook 8401029.001234.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 :

sql
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 :

sql
SELECT id_ticket, statut, priorite
FROM ticket
WHERE statut = 'ouvert' OR statut = 'en_cours' AND priorite = 'critique'
ORDER BY id_ticket;
id_ticketstatutpriorite
7ouvertnormale
12ouvertbasse
13en_courscritique
14ouvertnormale

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 :

sql
SELECT id_ticket, statut, priorite
FROM ticket
WHERE (statut = 'ouvert' OR statut = 'en_cours') AND priorite = 'critique';
id_ticketstatutpriorite
13en_courscritique

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.

sql
SELECT id_ticket, titre FROM ticket WHERE titre LIKE '%Odoo%';
id_tickettitre
3Mot de passe Odoo oublié
5Odoo 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 :

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

sql
SELECT nom, prenom FROM employe WHERE telephone = NULL;    -- aucune ligne, jamais
SELECT nom, prenom FROM employe WHERE telephone IS NULL;   -- la bonne écriture
nomprenom
PetitThomas
BonnetInès
MartinPaul

Les tables de vérité avec UNKNOWN :

ABA AND BA OR BNOT A
TRUEUNKNOWNUNKNOWNTRUEFALSE
FALSEUNKNOWNFALSEUNKNOWNTRUE
UNKNOWNUNKNOWNUNKNOWNUNKNOWNUNKNOWN

Conséquence moins visible : une condition négative ignore aussi les NULL.

sql
SELECT libelle, etage FROM service WHERE etage <> 2;
libelleetage
Direction3
Commercial1
Logistique0

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.

sql
SELECT libelle, etage FROM service ORDER BY etage, libelle;
libelleetage
JuridiqueNULL
Logistique0
Commercial1
Comptabilité2
Informatique2
Direction3

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 :

sql
SELECT num_inventaire, prix_achat
FROM poste
ORDER BY prix_achat DESC, num_inventaire
LIMIT 3;
num_inventaireprix_achat
PC-00121249.00
PC-00141249.00
PC-00061189.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 :

sql
-- 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_tickettitredate_ouverture
9Rançongiciel suspecté2026-10-05 08:05:00
8Pièce jointe trop lourde2026-10-01 11:12:00
7Installer Sage pour Paul Martin2026-09-23 09:00:00
6Ventilateur bruyant2026-09-22 15:40:00
5Odoo très lent2026-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 :

1FROM et JOINlit et combine les tablesécrit en 2e2WHEREgarde les lignes utilesécrit en 3e3GROUP BYforme les groupesécrit en 4e4HAVINGgarde les groupes utilesécrit en 5e5SELECTcalcule colonnes et aliasécrit en 1er6DISTINCTretire les doublonsavec SELECT7ORDER BYtrie le résultatécrit en 6e8LIMITgarde les n premièresécrit en 7e
Ordre logique d'évaluation d'un SELECT et rang de chaque clause dans l'ordre d'écriture

Cet ordre explique des erreurs qui semblent arbitraires :

sql
SELECT num_inventaire, ROUND(prix_achat * 1.2, 2) AS prix_ttc
FROM poste
WHERE prix_ttc > 1300;
text
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
sql
SELECT nom, prenom, date_embauche
FROM employe
WHERE date_embauche >= '2020-01-01' AND date_embauche < '2024-01-01'
ORDER BY date_embauche;
nomprenomdate_embauche
LambertChloé2020-09-01
RouxCamille2021-05-17
BonnetInès2022-10-03
DialloMoussa2023-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
sql
SELECT id_ticket, titre, priorite, statut
FROM ticket
WHERE statut <> 'resolu'
  AND priorite IN ('haute', 'critique')
ORDER BY id_ticket;
id_tickettitreprioritestatut
11Accès VPN impossiblehauteen_cours
13Lenteurs réseau à l'entrepôtcritiqueen_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
sql
SELECT id_ticket, titre
FROM ticket
WHERE id_poste IS NULL
ORDER BY id_ticket;
id_tickettitre
2Imprimante du 2e étage hors ligne
4Créer le compte de Paul Martin
7Installer Sage pour Paul Martin
13Lenteurs 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
sql
SELECT num_inventaire, prix_achat
FROM poste
ORDER BY prix_achat, num_inventaire
LIMIT 4 OFFSET 8;
num_inventaireprix_achat
PC-00051029.00
PC-00101099.00
PC-00111099.00
PC-00021149.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 ?

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

sql
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é ?

Chapitre terminé !

Fais les 5 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Fonctions SQL : dates, chaînes et CASERevoir le sommaire du cours