Requêtes SQL avec critères et fonctions d'agrégation

Objectifs Pédagogiques
  • Savoir filtrer les données avec des conditions simples ou multiples.
  • Utiliser efficacement les fonctions d'agrégation (COUNT, AVG, SUM, etc.).
  • Maîtriser les regroupements (GROUP BY) et les filtres d'agrégats (HAVING).
  • Formuler des requêtes imbriquées simples sans utiliser de jointures.

Introduction

Le chapitre précédent a posé les bases du SQL : créer des tables, insérer des données, faire des sélections simples et des jointures. Ce chapitre va plus loin : on apprend à interroger finement les données avec des conditions élaborées, des calculs statistiques, et des requêtes qui s'appellent entre elles.

Ces techniques sont celles que vous utiliserez le plus en pratique pour analyser et exploiter des bases de données réelles.


La grammaire d'une requête SQL

SELECT colonnes
FROM table
[WHERE condition]
[GROUP BY colonne]
[HAVING condition]
[ORDER BY colonne]
[LIMIT n];

Chaque clause joue un rôle précis. Ce qui rend SQL particulier : l'ordre d'écriture n'est pas l'ordre d'exécution.

OrdreClauseRôle
1FROMCharge la table source de données
2WHEREFiltre les lignes avant tout calcul
3GROUP BYRegroupe les lignes ayant des colonnes identiques
4HAVINGFiltre les groupes après GROUP BY
5SELECTCalcule et sélectionne les colonnes à afficher
6DISTINCTSupprime les doublons dans les résultats
7ORDER BYTrie les résultats affichés
8LIMITLimite le nombre de lignes retournées
À retenir - Ordre d'exécution
La clause FROM est traitée en premier, même si elle est écrite après SELECT.
C'est pourquoi on ne peut pas utiliser un alias défini dans SELECT à l'intérieur d'un WHERE : quand WHERE s'exécute, SELECT n'a pas encore calculé l'alias.
En revanche, ORDER BY s'exécute après SELECT, donc les alias y sont accessibles.

Filtres avec WHERE

SELECT TitreFilm, AnneeSortie
FROM FILM
WHERE Pays = 'USA' AND AnneeSortie BETWEEN 2000 AND 2010;

Utilisation de plusieurs conditions avec AND, OR, BETWEEN, LIKE :

SELECT NomActeur, PrenomActeur
FROM ACTEURS
WHERE NomActeur LIKE 'Mac%' OR DateNaissanceActeur BETWEEN '1980-01-01' AND '1990-12-31';

Opérateurs disponibles dans WHERE

OpérateurDescriptionExemple
=Egal aPays = 'USA'
<> ou !=Different deAnneeSortie <> 2023
>, <, >=, <=Comparaison numerique ou alphabetiqueBudget >= 1000000
BETWEEN ... ANDValeur comprise dans un intervalle (bornes incluses)AnneeSortie BETWEEN 2000 AND 2010
IN (...)Appartenance a une listePays IN ('USA', 'France', 'UK')
NOT IN (...)Exclusion d'une listePays NOT IN ('Chine', 'Russie')
LIKECorrespondance partielle avec des jokersTitreFilm LIKE 'Star%'
IS NULLTeste si une valeur est nulleDateSortie IS NULL
IS NOT NULLTeste si une valeur est non nulleDateSortie IS NOT NULL

Utilisation de jokers avec LIKE

JokerSignificationExemple
%Remplace n'importe quelle suite de caracteres (y compris vide)'Mac%' trouve MacDonald, Macbeth, Mac
_Remplace exactement un seul caractere'M_c' trouve Mac, Mec, Mic
Combiner plusieurs conditions avec des parenthèses
-- Tous les films américains ou britanniques sortis après 2010
WHERE (Pays = 'USA' OR Pays = 'UK') AND AnneeSortie > 2010

Les parenthèses sont essentielles : sans elles, le AND étant prioritaire sur le OR, l'interprétation serait différente.

Le piège des valeurs NULL

Attention - Comparaison avec NULL
En SQL, NULL signifie "valeur inconnue". On ne peut pas comparer NULL avec = ou !=.
-- ❌ Ne retourne RIEN, même si des lignes ont DateSortie = NULL
WHERE DateSortie = NULL

-- ✅ Correct
WHERE DateSortie IS NULL

-- ❌ N'inclut pas les lignes avec NULL dans DateSortie
WHERE DateSortie != '2020-01-01'

-- ✅ Inclut les NULL
WHERE DateSortie != '2020-01-01' OR DateSortie IS NULL
A retenir
La clause WHERE filtre les lignes individuelles avant tout calcul.
Une bonne utilisation des operateurs permet d'extraire precisement les donnees voulues.

Fonctions d'agrégation

Les fonctions d'agrégation calculent une valeur à partir de plusieurs lignes. Elles sont incontournables pour produire des statistiques.

FonctionRoleRemarque
COUNT(*)Nombre total de lignesCompte toutes les lignes, y compris les NULL
COUNT(col)Nombre de valeurs non NULL dans colIgnore les NULL
SUM(col)Somme des valeursIgnore les NULL
AVG(col)Moyenne des valeursIgnore les NULL
MIN(col)Valeur minimaleFonctionne aussi sur des dates et textes
MAX(col)Valeur maximaleFonctionne aussi sur des dates et textes

Exemples sur l'ensemble de la table

-- Nombre de films sortis en 2009
SELECT COUNT(*) AS nb_films
FROM FILM
WHERE AnneeSortie = 2009;
-- Plus petit et plus grand budget de la base
SELECT MIN(Budget) AS budget_min, MAX(Budget) AS budget_max
FROM FILM;
-- Budget moyen arrondi des films américains
SELECT ROUND(AVG(Budget), 2) AS budget_moyen
FROM FILM
WHERE Pays = 'USA';

Regrouper avec GROUP BY

GROUP BY divise les lignes en groupes selon une colonne, et les fonctions d'agrégation s'appliquent à chaque groupe séparément.

Visualiser l'effet de GROUP BY

Table FILM (extrait) :

TitreFilmPaysBudget
InceptionUSA160000000
InterstellarUSA165000000
ParasiteCoree11400000
TitanicUSA200000000
OkjaCoree50000000
SELECT Pays, COUNT(*) AS nb_films, AVG(Budget) AS budget_moyen
FROM FILM
GROUP BY Pays;

Résultat :

Paysnb_filmsbudget_moyen
USA3175000000
Coree230700000

Chaque groupe (USA, Coree) produit une seule ligne de résultat. Les 5 lignes d'origine sont résumées en 2.

-- Budget total cumulé par année
SELECT AnneeSortie, SUM(Budget) AS total_budget
FROM FILM
GROUP BY AnneeSortie;
Règle fondamentale du GROUP BY
Dans un SELECT avec GROUP BY, chaque colonne affichée doit être soit :
- dans la clause GROUP BY, soit une fonction d'agrégation (COUNT, AVG, etc.)

Sinon, la requête est invalide (ou retourne un résultat imprévisible selon le SGBD).
A retenir
Les fonctions d'agrétion s'utilisent dans la clause SELECT.
Elles calculent une valeur unique par groupe ou sur l'ensemble des lignes.
Pour les appliquer à plusieurs groupes, utilisez GROUP BY.
Pour filtrer les résultats agrégés, utilisez HAVING (pas WHERE).

Filtrer après regroupement : HAVING

HAVING s'applique après GROUP BY pour filtrer les groupes selon une condition sur les agrégats.

-- Pays ayant produit plus de 5 films
SELECT Pays, COUNT(*) AS nb
FROM FILM
GROUP BY Pays
HAVING nb > 5;
-- Années dont le budget cumulé dépasse 500 millions
SELECT AnneeSortie, SUM(Budget) AS total_budget
FROM FILM
GROUP BY AnneeSortie
HAVING total_budget > 500000000;

Combiner WHERE et HAVING

Les deux clauses peuvent coexister : elles n'agissent pas au même moment.

-- Années avec au moins 3 films américains et un budget moyen supérieur à 10M
SELECT AnneeSortie, COUNT(*) AS nb_films, AVG(Budget) AS budget_moyen
FROM FILM
WHERE Pays = 'USA'                       -- 1. filtre les lignes (films USA seulement)
GROUP BY AnneeSortie                     -- 2. groupe par année
HAVING nb_films >= 3                     -- 3. filtre les groupes
  AND AVG(Budget) > 10000000;

Différence entre WHERE et HAVING

WHEREHAVING
QuandAvant le regroupementAprès le GROUP BY
Sur quoiLes lignes individuellesLes groupes
Agrégats autorisésNonOui (COUNT, AVG, etc.)
-- ❌ Erreur : AVG ne peut pas être utilisé dans WHERE
SELECT Pays FROM FILM
WHERE AVG(Budget) > 10000000;

-- ✅ Correct
SELECT Pays FROM FILM
GROUP BY Pays
HAVING AVG(Budget) > 10000000;
A retenir
Utilisez WHERE pour filtrer les lignes individuelles (avant regroupement).
Utilisez HAVING pour filtrer les groupes formés par GROUP BY.
Les deux peuvent être combinés dans une même requête - ils s'appliquent à des moments différents.

Requêtes imbriquées (sous-requêtes)

Une sous-requête est un SELECT placé à l'intérieur d'une autre requête. Elle est exécutée en premier, et son résultat est utilisé par la requête externe.

Les trois types de sous-requêtes
  • Scalaire : retourne une seule valeur - utilisée avec =, >, <...
  • Multi-valeur : retourne une colonne de valeurs - utilisée avec IN / NOT IN
  • Corrélée : fait référence à la requête externe, réévaluée ligne par ligne

Sous-requêtes scalaires

-- Films dont le budget est supérieur à la moyenne
SELECT TitreFilm, Budget
FROM FILM
WHERE Budget > (
  SELECT AVG(Budget) FROM FILM
);
-- Film(s) ayant le budget maximum
SELECT TitreFilm, Budget
FROM FILM
WHERE Budget = (
  SELECT MAX(Budget) FROM FILM
);
-- Films sortis la même année qu'Inception
SELECT TitreFilm
FROM FILM
WHERE AnneeSortie = (
  SELECT AnneeSortie FROM FILM WHERE TitreFilm = 'Inception'
);
Attention - Sous-requête scalaire
Une sous-requête utilisée avec =, >, etc. doit retourner exactement une valeur.
Si elle retourne plusieurs lignes, le SGBD retourne une erreur.
Utilisez MAX(), MIN(), AVG() pour garantir un résultat unique.

Sous-requêtes avec IN et NOT IN

-- Acteurs plus jeunes que le plus ancien enregistré
SELECT NomActeur, PrenomActeur
FROM ACTEURS
WHERE DateNaissanceActeur > (
  SELECT MIN(DateNaissanceActeur) FROM ACTEURS
);
-- Films dont le budget correspond à celui d'un film de 2020
SELECT TitreFilm
FROM FILM
WHERE Budget IN (
  SELECT Budget FROM FILM WHERE AnneeSortie = 2020
);
-- Films dont le titre n'existe pas en 2010
SELECT TitreFilm
FROM FILM
WHERE TitreFilm NOT IN (
  SELECT TitreFilm FROM FILM WHERE AnneeSortie = 2010
);
Piège de NOT IN avec des NULL
Si la sous-requête retourne au moins un NULL, NOT IN ne retournera aucune ligne.
C'est un comportement logiquement cohérent mais contre-intuitif.
En cas de doute, préférez NOT EXISTS qui gère correctement les NULL.

Requêtes avancées sans jointure

Nombre de films par année (seulement si >= 3)

SELECT AnneeSortie, COUNT(*) AS nb
FROM FILM
GROUP BY AnneeSortie
HAVING nb >= 3
ORDER BY AnneeSortie;

Top 3 des années avec le plus gros budget cumulé

SELECT AnneeSortie, SUM(Budget) AS total
FROM FILM
GROUP BY AnneeSortie
ORDER BY total DESC
LIMIT 3;

Film le plus cher de chaque année (sous-requête corrélée)

SELECT f1.TitreFilm, f1.AnneeSortie, f1.Budget
FROM FILM f1
WHERE f1.Budget = (
  SELECT MAX(f2.Budget)
  FROM FILM f2
  WHERE f2.AnneeSortie = f1.AnneeSortie
);

La sous-requête est corrélée : elle est réévaluée pour chaque ligne de f1, en cherchant le budget maximum de la même année.

Réalisateurs ayant sorti plus de films que la moyenne

SELECT NomRealisateur, PrenomRealisateur, COUNT(*) AS nb_films
FROM FILM
GROUP BY NomRealisateur, PrenomRealisateur
HAVING nb_films > (
  SELECT AVG(nb)
  FROM (
    SELECT COUNT(*) AS nb
    FROM FILM
    GROUP BY NomRealisateur, PrenomRealisateur
  ) AS sous_stats
);
Comment lire cette requête complexe

Elle se lit de l'intérieur vers l'extérieur :

  1. La sous-requête la plus interne calcule le nombre de films par réalisateur.
  2. La sous-requête intermédiaire calcule la moyenne de ces nombres.
  3. La requête externe ne garde que les réalisateurs dépassant cette moyenne.

Opérateurs ALL et ANY

-- Films strictement plus chers que TOUS les autres films de leur année
SELECT f1.TitreFilm, f1.AnneeSortie, f1.Budget
FROM FILM f1
WHERE f1.Budget > ALL (
  SELECT f2.Budget
  FROM FILM f2
  WHERE f2.AnneeSortie = f1.AnneeSortie AND f2.TitreFilm <> f1.TitreFilm
);
-- Films plus chers qu'AU MOINS UN film de 2010
SELECT TitreFilm, Budget
FROM FILM
WHERE Budget > ANY (
  SELECT Budget FROM FILM WHERE AnneeSortie = 2010
);
ALL vs ANY
ALL : la condition doit être vraie pour tous les éléments de la sous-requête.
ANY (ou SOME) : la condition doit être vraie pour au moins un élément.
= ANY (...) est équivalent à IN (...).
A retenir
Les requêtes avancées peuvent combiner GROUP BY, HAVING, ORDER BY et sous-requêtes.
Les sous-requêtes corrélées permettent de croiser des données entre lignes sans jointure explicite.
Les opérateurs ALL et ANY permettent des comparaisons ensemblistes puissantes.

Tri des résultats : ORDER BY

-- Films américains triés par budget décroissant
SELECT TitreFilm, AnneeSortie, Budget
FROM FILM
WHERE Pays = 'USA'
ORDER BY Budget DESC;

Trier par plusieurs colonnes

-- D'abord par année croissante, puis par budget décroissant dans chaque année
SELECT TitreFilm, AnneeSortie, Budget
FROM FILM
ORDER BY AnneeSortie ASC, Budget DESC;

L'ordre de tri est appliqué colonne par colonne : si deux films ont la même année, le budget départage.

Trier après agrégation

-- Années triées par nombre de films décroissant
SELECT AnneeSortie, COUNT(*) AS nb_films
FROM FILM
GROUP BY AnneeSortie
ORDER BY nb_films DESC;

On peut utiliser l'alias défini dans SELECT directement dans ORDER BY (car ORDER BY s'exécute après SELECT).

Trier les valeurs NULL

Le comportement par défaut des NULL varie selon le SGBD. Pour forcer leur position en MySQL :

-- Dates connues en premier, NULL en dernier
ORDER BY DateSortie IS NULL ASC, DateSortie ASC;

DateSortie IS NULL retourne 0 (faux) pour les valeurs renseignées et 1 (vrai) pour les NULL. En triant ASC, les 0 viennent avant les 1.

Bonnes pratiques ORDER BY
  • Utilisez ORDER BY pour faciliter la lecture et la hiérarchisation des résultats.
  • Combinez GROUP BY, HAVING, et ORDER BY pour construire des requêtes puissantes.
  • Utilisez toujours des aliases lisibles dans SELECT, surtout quand vous triez sur des agrégats.
  • ASC (croissant) est le comportement par défaut - ne l'écrivez que si vous voulez être explicite.