Probleme – Deux agrégats dans une requête, et pourquoi ils sont faux
Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 21 — Bases de données relationnelles et SQL
Énoncé
On veut, par promotion, le nombre de notes et le nombre d'enseignements.
- Écrire les deux requêtes séparément.
- Les fusionner en une seule. Que rend-elle ?
- Expliquer le mécanisme, et le mesurer sur
SUM. - Donner deux réparations.
Corrigé
1. Séparément, les deux requêtes sont sans mystère.
SELECT e.promo, COUNT(*) AS nb_notes
FROM Eleve e JOIN Note n ON n.eleve = e.id GROUP BY e.promo;
-- 2026 : 4 | 2027 : 1
SELECT promo, COUNT(*) AS nb_enseignements FROM Enseigne GROUP BY promo;
-- 2026 : 4 | 2027 : 2
2. Fusionnées, elles mentent.
SELECT e.promo, COUNT(*) AS lignes
FROM Eleve e
JOIN Note n ON n.eleve = e.id
JOIN Enseigne g ON g.promo = e.promo
GROUP BY e.promo;
-- 2026 : 16 2027 : 2
Seize lignes pour , là où l'on en attendait quatre. Et si l'on avait écrit COUNT(n.valeur) d'un côté et COUNT(g.prof) de l'autre, les deux auraient rendu .
3. Le mécanisme, et il est arithmétique. Les deux jointures sont indépendantes : rien ne relie une note à un enseignement. Le moteur produit donc, pour chaque note de la promotion, toutes les lignes d'enseignement de cette promotion — soit pour , et pour . C'est un produit cartésien à l'intérieur de chaque paquet, et le GROUP BY n'y peut rien : il compte ce qu'on lui donne.
Sur SUM, le dégât est plus visible encore :
| `SUM(n.valeur)` avec les deux jointures | la vraie somme | |
|---|---|---|
et : chaque note a été comptée autant de fois qu'il y a d'enseignements. Le facteur n'est pas constant — il vaut pour une promotion et pour l'autre — de sorte qu'aucune division ne rattrapera l'erreur en bloc.
Rien ne signale la faute : pas d'erreur, pas de NULL, pas de ligne manquante. Seulement des nombres trop grands, dans des proportions qui varient d'un groupe à l'autre. Sur un tableau de bord, cela donne un chiffre d'affaires quadruplé pour une région et doublé pour une autre — et l'on peut en tirer des décisions pendant des mois.
Le symptôme qui doit alerter est toujours le même : un agrégat qui change quand on ajoute une jointure sans rapport avec lui. C'est le test à faire, et il coûte une requête.
4. Les deux réparations.
(a) Agréger avant de joindre. On calcule chaque total dans sa propre requête, puis on rapproche les résultats.
SELECT a.promo, a.nb_notes, b.nb_enseignements
FROM (SELECT e.promo, COUNT(*) AS nb_notes FROM Eleve e
JOIN Note n ON n.eleve = e.id GROUP BY e.promo) a
JOIN (SELECT promo, COUNT(*) AS nb_enseignements FROM Enseigne
GROUP BY promo) b ON b.promo = a.promo;
-- 2026 | 4 | 4 2027 | 1 | 2
Chaque sous-requête ne voit qu'une seule table de détail : aucune multiplication n'est possible. C'est la réparation générale, et la seule qui fonctionne pour SUM et AVG.
(b) Compter des entités distinctes. Quand la grandeur voulue est un nombre d'objets et non un nombre de lignes, COUNT(DISTINCT ...) traverse la duplication :
SELECT e.promo, COUNT(DISTINCT n.eleve) AS eleves_notes,
COUNT(DISTINCT g.prof) AS professeurs
FROM Eleve e JOIN Note n ON n.eleve = e.id
JOIN Enseigne g ON g.promo = e.promo
GROUP BY e.promo;
-- 2026 | 2 | 3 2027 | 1 | 2
Vérifié : les mêmes chiffres sortent des deux requêtes séparées. Mais cette réparation ne marche que pour COUNT — il n'existe pas de « SUM DISTINCT » qui aurait un sens, puisque deux notes égales doivent bien être additionnées deux fois.
Une requête ne doit joindre qu'une table de détail par agrégat. Dès qu'il y en a deux qui ne sont pas reliées entre elles, les lignes se multiplient et tous les agrégats sont faux — silencieusement, et d'un facteur qui varie par groupe.
C'est le même défaut que le produit cartésien de l'exercice 21.5, mais il se cache mieux : ici, les deux jointures sont écrites correctement, chacune avec son ON. Le compte des ON du problème 21.2 est satisfait. Ce qui manque n'est pas une condition, c'est un lien qui n'existe pas — et aucune vérification syntaxique ne peut le voir.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.