Parfois, on a besoin non seulement de grouper des données et de filtrer le résultat, mais de le faire avec une logique supplémentaire — par exemple, comparer la note moyenne des étudiants dans un groupe avec un critère externe. C’est là que HAVING avec des sous-requêtes entre en jeu — un outil puissant qui te permet de prendre des décisions plus intelligentes directement dans ta requête SQL.
Petit rappel sur HAVING
On va se concentrer sur les sous-requêtes utilisées avec HAVING pour filtrer les données au niveau des valeurs agrégées. Pourquoi ? Parce que WHERE permet de filtrer les lignes individuelles, alors que HAVING s’applique déjà aux données groupées — c’est un autre niveau d’analyse qui élargit tes possibilités.
Avant de plonger dans la combinaison des sous-requêtes et de HAVING, faisons un petit rappel sur ce qu’est HAVING et en quoi il diffère de WHERE.
WHEREfiltre les lignes avant l’exécution du groupement (GROUP BY).HAVINGfiltre les données après l’agrégation, quand les données sont déjà groupées.
Imagine que tu analyses des étudiants et leurs notes. Avec WHERE, tu peux exclure les étudiants avec certaines notes minimales, alors que HAVING te permet d’exclure carrément des groupes d’étudiants selon leur moyenne ou leur note maximale.
Exemple de données
Voici une table d’exemple avec des étudiants :
Table students :
| student_id | student_name | department | grade |
|---|---|---|---|
| 1 | Alex | Physics | 80 |
| 2 | Maria | Physics | 85 |
| 3 | Dan | Math | 90 |
| 4 | Lisa | Math | 60 |
| 5 | John | History | 70 |
Exemple d’utilisation de HAVING (sans sous-requêtes)
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > 75;
Résultat :
| department | avg_grade |
|---|---|
| Physics | 82.5 |
| Math | 75.0 |
La fac "History" n’apparaît pas dans le résultat, parce que sa moyenne est en dessous de 75. Facile, non ? Maintenant, ajoutons un peu de magie avec les sous-requêtes. Dans l’exemple suivant, on peut par exemple filtrer en comparant avec la moyenne générale de toutes les facs.
Sous-requêtes dans HAVING
Les sous-requêtes dans HAVING — c’est super pratique pour ajouter de la flexibilité quand tu filtres des données agrégées. Elles te permettent de comparer des agrégats, comme la moyenne ou le max, avec des valeurs calculées ailleurs dans la base. En gros, tu peux vérifier : "Est-ce que notre résultat est meilleur que la moyenne générale ?"
Exemple : filtrer les facs par note moyenne
Imaginons qu’on veuille trouver les facs où les étudiants bossent mieux que les autres — c’est-à-dire où la moyenne est supérieure à la moyenne de l’université.
Voici nos données :
Table students :
| student_id | student_name | department | grade |
|---|---|---|---|
| 1 | Alex | Physics | 80 |
| 2 | Maria | Physics | 85 |
| 3 | Dan | Math | 90 |
| 4 | Lisa | Math | 60 |
| 5 | John | History | 70 |
D’abord, on récupère la moyenne de tous les étudiants :
SELECT AVG(grade) AS university_avg
FROM students;
Maintenant, on applique une sous-requête dans HAVING :
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);
Résultat :
| department | avg_grade |
|---|---|
| Physics | 82.5 |
Qu’est-ce qui se passe ici ?
- La sous-requête (
SELECT AVG(grade) FROM students) calcule la moyenne générale — ici, c’est 77. - La requête principale groupe les étudiants par fac et calcule la moyenne pour chaque fac.
HAVINGcompare la moyenne de la fac avec la moyenne générale et ne garde que celles qui sont au-dessus.
Comparaison entre WHERE et HAVING
Pour bien piger la différence, imagine que tu veux sélectionner seulement les étudiants qui ont une note au-dessus de la moyenne. Ça, tu peux le faire uniquement avec WHERE :
SELECT name, grade
FROM students
WHERE grade > (SELECT AVG(grade) FROM students);
Résultat (si on prend la table des exemples précédents) :
| name | grade |
|---|---|
| Alex | 80 |
| Maria | 85 |
| Dan | 90 |
Mais si tu veux voir dans quelles facs la moyenne des étudiants est supérieure à la moyenne de l’université, là tu ne peux pas te passer de HAVING — parce que tu filtres des groupes, pas des lignes :
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);
Résultat :
| department | avg_grade |
|---|---|
| Physics | 82.5 |
En résumé :
WHEREagit sur les lignes individuelles avant le groupement.HAVINGfiltre les groupes après qu’ils aient été agrégés.
Exemple : bosser avec plusieurs agrégats
Regardons un autre cas. Disons qu’on a une table students qui contient les notes des étudiants et leurs facs :
Table students :
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
Maintenant, on veut trouver les facs où :
- La moyenne des étudiants est supérieure à la moyenne de l’université.
- La note maximale dans la fac est supérieure à 90.
Pour ça, on écrit cette requête :
SELECT department, AVG(grade) AS avg_grade, MAX(grade) AS max_grade
FROM students
GROUP BY department
HAVING AVG(grade) > ( SELECT AVG(grade) FROM students )
AND MAX(grade) > 90;
Ce qui se passe dans cette requête :
AVG(grade)> (SELECT AVG(grade) FROM students) — on vérifie que la fac est en moyenne meilleure que les autres.MAX(grade)> 90 — ça veut dire qu’il y a quelqu’un qui a cartonné à l’exam.
Résultat :
| department | avg_grade | max_grade |
|---|---|---|
| Math | 92.5 | 95 |
La fac "Math" est la seule à avoir à la fois une moyenne supérieure à la générale et un étudiant qui a eu plus de 90.
Exemple : sélectionner les groupes avec l’écart minimal
Imaginons que tu veuilles trouver les groupes où la différence entre la note max et la note min des étudiants est plus petite que la différence dans l’université en général.
Voici la table students avec laquelle on va bosser :
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
On va découper la tâche en étapes :
- D’abord, on calcule la différence max-min pour toute l’université :
SELECT MAX(grade) - MIN(grade) AS range_university FROM students; - Maintenant, on crée la requête principale et on la combine avec cette sous-requête :
SELECT department, MAX(grade) - MIN(grade) AS range_department
FROM students
GROUP BY department
HAVING (MAX(grade) - MIN(grade)) < ( SELECT MAX(grade) - MIN(grade) FROM students );
Résultat de la requête :
| department | range_department |
|---|---|
| Physics | 5 |
| Math | 5 |
Les groupes "Physics" et "Math" ont montré des notes plus stables — leur écart est plus petit que celui de l’université en général.
Optimisation des requêtes avec HAVING et sous-requêtes
Faut garder en tête que les sous-requêtes imbriquées peuvent avoir un gros impact sur les perfs, surtout dans les grosses bases. Voilà quelques tips :
Utilise des index. Si la sous-requête s’exécute sur une colonne utilisée dans WHERE ou JOIN, assure-toi qu’il y a un index dessus.
Évite la surcharge de données. Si la sous-requête retourne trop de résultats intermédiaires, découpe-la en étapes ou utilise des tables temporaires.
Profile tes requêtes avec EXPLAIN. Vérifie toujours comment PostgreSQL exécute ta requête. Si tu vois que la sous-requête s’exécute plein de fois, pense à l’optimiser.
Compare avec CTE. Parfois, utiliser WITH (Common Table Expressions) peut être plus rapide et plus lisible. Mais ça, on en parlera dans les prochaines leçons :P
Combiner sous-requêtes, HAVING et GROUP BY
Avec les sous-requêtes dans HAVING, tu peux construire des filtres plus complexes, surtout quand tu dois prendre en compte à la fois des agrégats, des moyennes et d’autres métriques. Tout ça aide à trouver des insights sympas dans les vraies données.
Exemple : comparer les facs par moyenne et nombre d’étudiants
Imaginons que tu veuilles sélectionner les facs où :
- La moyenne est supérieure à la moyenne de l’université.
- Le nombre d’étudiants est supérieur à celui de la fac avec la plus basse moyenne.
Voici la table de départ students :
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
| Oleg | 60 | History |
La requête :
SELECT department, AVG(grade) AS avg_grade, COUNT(*) AS student_count
FROM students
GROUP BY department
HAVING AVG(grade) > ( SELECT AVG(grade) FROM students )
AND COUNT(*) > (
SELECT COUNT(*)
FROM students
GROUP BY department
ORDER BY AVG(grade)
LIMIT 1
);
Cette requête montre comment combiner les sous-requêtes dans HAVING et GROUP BY pour analyser selon plusieurs critères à la fois. Résultat :
| department | avg_grade | student_count |
|---|---|---|
| Physics | 82.5 | 2 |
| Math | 92.5 | 2 |
La fac History n’apparaît pas dans le résultat, parce qu’elle a la plus basse moyenne et le plus petit nombre d’étudiants. Physics et Math — les deux sont au-dessus de la moyenne, que ce soit pour les notes ou pour le nombre d’étudiants.
Erreurs courantes et comment les éviter
Erreur avec NULL. Si les données contiennent des NULL, les sous-requêtes avec HAVING peuvent donner des résultats inattendus. Utilise COALESCE pour gérer ces cas :
SELECT AVG(grade)
FROM students
WHERE grade IS NOT NULL;
Données inutiles dans la sous-requête. Si la sous-requête retourne trop de résultats, ça va impacter les perfs. Précise toujours bien les conditions de ta sous-requête.
Mauvaise compréhension de l’ordre d’exécution. Rappelle-toi que HAVING s’exécute après le groupement, et que les sous-requêtes peuvent s’exécuter avant la requête principale.
Absence d’index. Si les colonnes utilisées dans la sous-requête ne sont pas indexées, ça va sérieusement ralentir l’exécution.
Les sous-requêtes dans HAVING t’ouvrent plein de possibilités pour analyser les données au niveau des agrégats. Tu peux filtrer des groupes selon des conditions complexes, comparer les résultats entre groupes et créer des requêtes analytiques avancées. Bravo, maintenant tu es prêt à utiliser ces connaissances dans des vrais projets !
GO TO FULL VERSION