Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Agrégations SQL : COUNT, GROUP BY, HAVING et fonctions de dates

Par mis à jour le

Améliorer cette page

Une agrégation résume plusieurs lignes en une seule valeur : count(*) compte, sum additionne, avg fait la moyenne ; avec GROUP BY, on obtient une ligne de résumé par groupe, par exemple un nombre de déploiements par environnement. C'est la base de tout tableau de bord d'exploitation : taux de réussite des déploiements, durée moyenne, alertes par serveur. Cette leçon pratique les agrégats, GROUP BY, HAVING et les fonctions de dates et de chaînes sur la base fil rouge, sous PostgreSQL 18.6, avec deux pièges qui rendent un chiffre faux sans erreur.

  • Résumer une table avec COUNT, SUM, AVG, MIN et MAX.
  • Regrouper avec GROUP BY et filtrer les groupes avec HAVING.
  • Calculer un taux sans tomber dans la division entière.
  • Regrouper et calculer sur des dates et des chaînes.

Les leçons Syntaxe de base et NULL et valeurs absentes, qui explique comment les agrégats traitent les valeurs absentes.

Sans GROUP BY, une fonction d'agrégation résume toute la table, ou toutes les lignes que garde WHERE, en une seule ligne :

SELECT count(*) AS deploiements,
min(deployed_at) AS premier,
max(deployed_at) AS dernier,
round(avg(duration_seconds), 1) AS duree_moy,
sum(duration_seconds) AS duree_totale
FROM deployments;
deploiements | premier | dernier | duree_moy | duree_totale
--------------+------------------------+------------------------+-----------+--------------
25 | 2026-01-15 09:00:00+00 | 2026-04-13 02:30:00+00 | 77.4 | 1934

round(valeur, 1) arrondit à une décimale. La moyenne cache souvent une distribution déséquilibrée : la médiane, que PostgreSQL calcule avec percentile_cont(0.5) WITHIN GROUP (ORDER BY duration_seconds), vaut 48 secondes, quand la moyenne en affiche 77,4. Quelques déploiements de base de données, à 300 secondes, tirent la moyenne vers le haut.

GROUP BY découpe les lignes en groupes de même valeur, et calcule les agrégats par groupe :

SELECT environment, count(*) AS n, round(avg(duration_seconds)) AS duree_moy
FROM deployments
GROUP BY environment
ORDER BY n DESC;
environment | n | duree_moy
-------------+----+-----------
production | 17 | 71
staging | 7 | 102
development | 1 | 10

On peut regrouper sur plusieurs colonnes : GROUP BY environment, status donne une ligne par combinaison, et montre les 2 rollbacks de production et les 2 échecs de staging.

Dans une requête groupée, chaque colonne du SELECT doit être dans le GROUP BY ou dans une agrégation : un groupe de 17 déploiements n'a pas une seule version. PostgreSQL refuse :

ERROR: column "deployments.version" must appear in the GROUP BY clause or be used in an aggregate function

MySQL 8.4 refuse aussi, grâce au mode ONLY_FULL_GROUP_BY activé par défaut. SQLite, lui, accepte la même requête et affiche une version arbitraire par groupe, 2.1.0 pour la production, sans le moindre avertissement : un chiffre faux qui a l'air juste. Une requête mise au point sous SQLite peut donc échouer en production sous PostgreSQL, et c'est tant mieux.

WHERE filtre les lignes avant le regroupement ; HAVING filtre les groupes après. Une condition sur un agrégat va donc dans HAVING : WHERE count(*) > 5 est refusé avec aggregate functions are not allowed in WHERE. Pour trouver les membres de l'équipe qui ont au moins deux déploiements en échec ou annulés :

SELECT u.username, count(*) AS echecs
FROM deployments d
JOIN users u ON u.id = d.deployed_by
WHERE d.status IN ('failed', 'rollback') -- avant : quelles lignes
GROUP BY u.username
HAVING count(*) >= 2 -- après : quels groupes
ORDER BY echecs DESC;

Mesuré, un seul nom ressort : agarcia, avec 3. Garder dans WHERE tout ce qui peut y aller est aussi plus efficace : moins de lignes à regrouper.

Un taux de réussite se calcule en divisant deux comptes. FILTER, une clause du standard SQL que PostgreSQL implémente, restreint un agrégat à certaines lignes, sans sous-requête :

SELECT environment,
count(*) AS total,
count(*) FILTER (WHERE status = 'success') AS reussis,
round(100.0 * count(*) FILTER (WHERE status = 'success') / count(*), 1) AS taux
FROM deployments
GROUP BY environment
ORDER BY environment;
environment | total | reussis | taux
-------------+-------+---------+-------
development | 1 | 1 | 100.0
production | 17 | 15 | 88.2
staging | 7 | 5 | 71.4

Le 100.0 n'est pas décoratif. count(*) rend un entier, et la division de deux entiers est entière : 7 / 2 vaut 3. Mesuré, la même requête écrite count(*) FILTER (...) / count(*) * 100 affiche un taux de 0 pour la production et le staging, parce que 15 / 17 vaut 0 avant même la multiplication. Commencer par 100.0 *, ou convertir avec ::numeric, fait passer tout le calcul en décimal.

Les colonnes de dates sont des timestamptz, avec leurs fonctions propres. date_trunc ramène une date au début de sa période, ce qui permet de regrouper par mois, jour ou heure :

SELECT date_trunc('month', deployed_at) AS mois, count(*)
FROM deployments
GROUP BY 1
ORDER BY 1;

Mesuré : 2 déploiements en janvier, 4 en février, 7 en mars, 12 en avril. GROUP BY 1 désigne la première colonne du SELECT, un raccourci commode. Les autres fonctions utiles :

FonctionExempleRésultat mesuré
extractextract(dow FROM deployed_at)jour de la semaine, 0 pour dimanche : 9 déploiements
to_charto_char(created_at, 'DD/MM/YYYY')15/06/2023
soustractionacknowledged_at - created_atun intervalle, 01:00:00 au plus long
avg d'intervallesavg(acknowledged_at - created_at)délai moyen d'acquittement, 00:17:30
intervalnow() - interval '24 hours'il y a 24 heures

Soustraire deux horodatages donne un interval, qui s'agrège comme un nombre : le délai moyen d'acquittement des alertes est de 17 minutes 30. Les données de la base fil rouge datent d'avril 2026 : une fenêtre « dernières 24 heures » calculée avec now() y est vide, d'où des exemples à date fixe.

Les fonctions de chaînes servent surtout à extraire une information rangée dans un texte. split_part(hostname, '-', 1) découpe le nom d'hôte sur le tiret et garde le premier morceau, le rôle du serveur, sur lequel on peut regrouper :

SELECT split_part(hostname, '-', 1) AS role, count(*)
FROM servers
GROUP BY 1
ORDER BY 2 DESC, 1;

Les serveurs db arrivent en tête, avec 3. string_agg fait l'inverse d'un découpage : elle assemble les valeurs d'un groupe en une chaîne, string_agg(hostname, ', ' ORDER BY hostname) liste les serveurs de chaque datacenter sur une ligne. upper, length et replace complètent la boîte à outils, et l'opérateur ~ teste une expression régulière : hostname ~ '^(web|app)-' garde app-staging-01, web-prod-01 et web-prod-02.

CAST(valeur AS type), ou sa forme courte PostgreSQL valeur::type, convertit une valeur : '42'::int + 1 vaut 43, '2026-04-01'::date est une date. Une conversion impossible est une erreur, pas un NULL : 'abc'::int échoue avec invalid input syntax for type integer: "abc". Sous SQLite, la même conversion rend 0 sans prévenir.

  • Une agrégation résume des lignes ; count(*) compte les lignes, count(colonne) les valeurs présentes.
  • Avec GROUP BY, chaque colonne du SELECT est groupée ou agrégée ; SQLite accepte l'inverse et affiche une valeur arbitraire.
  • WHERE filtre les lignes avant le regroupement, HAVING les groupes après.
  • La division de deux entiers est entière : un taux se calcule avec 100.0 * en tête, et FILTER compte un sous-ensemble.
  • date_trunc regroupe par période, la soustraction de deux dates donne un interval ; split_part et string_agg découpent et assemblent les chaînes.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn