Aller au menu - Aller au contenu

[Plan du site] Vous êtes ici --- > Le Site du Zér0 > Les tutoriels > Non-Officiels > Site Web > PHP > Base de données > Lecture du tutoriel

[SQL] Regroupez vos champs avec GROUP BY

Avatar
Auteur : Dentuk
Créé : le 22/04/2007 18:48:25
Modifié : le 19/04/2008 11:33:30
Noter et commenter ce tutoriel
Imprimer ce tutoriel
Vous vous apprêtez à lire un tutoriel rédigé par un membre de ce site. Malgré tout le soin que ce membre a pu apporter au tutoriel, nous ne pouvons pas garantir que les informations contenues sur cette page sont exactes à 100%. Merci de garder cela en tête lorsque vous lirez cette page ;o)
Ce tutoriel est un complément du chapitre "Lire des données" de la partie 2 du tutoriel officiel PHP.
Il est donc nécessaire d'avoir bien acquis ce dernier avant d'aller plus loin. :)

Nous allons ici voir comment regrouper nos entrées en agrégats (Déf. Agrégat), c'est-à-dire les regrouper selon un ou plusieurs champs qu'elles ont en commun.
Cela nous permettra par exemple de regrouper des articles par date, auteur ou autre.
Nous pourrons ensuite par le biais de fonctions récupérer le nombre d'entrées contenues dans l'agrégat et bien d'autres choses.
Pour les tests, nous utiliserons la même table que celle du tutoriel officiel.
Si vous ne l'avez plus, je vous invite à télécharger sa structure et son contenu.
Sommaire du chapitre :

La clause GROUP BY

Cette clause qui nous permet de regrouper nos entrées en agrégats s'utilise dans les requêtes de type SELECT.
Elle se place entre la clause WHEREet la clause ORDER BY.

Pour regrouper, il faut indiquer un champ selon lequel il faut regrouper.
Par exemple, dans notre table jeux_videos, si l'on veut regrouper les jeux par possesseur, on utilisera la clause suivante :
Code : SQL
1
GROUP BY possesseur

Qui aura pour effet de créer un agrégat pour possesseur = 'Corentin', un autre pour possesseur = 'Florent', et un autre pour chaque possesseur que je n'ai pas cité.

On peut également regrouper selon plusieurs champs.
Par exemple, pour créer un agrégat pour chaque console de chaque possesseur, on utilisera la clause suivante :
Code : SQL
1
GROUP BY possesseur, console

Qui aura pour effet de créer un agrégat possesseur = 'Corentin' et <police nom="courrier">console='PC', un autre possesseur = 'Corentin' et console = 'PS2' (pas plus d'agrégats pour Corentin car il ne propose que des jeux PC/PS2), un autre pour possesseur = 'Florent' et console = 'Dreamcast', et ainsi de suite pour chaque possesseur et chaque console.

Notez que GROUP BY ordonne les agrégats à la manière d'une clause ORDER BY.
C'est-à-dire qu'en groupant par possesseur, le premier agrégat sera l'agrégat possesseur = 'Corentin' et le dernier possesseur = 'Sebastien'.
On peut aussi classer les agrégats en ordre décroissant avec le même mot-clé que dans un ORDER BY, c'est-à-dire DESC.
Ainsi, l'agrégat possesseur = 'Sebastien' sera le premier, et l'agrégat possesseur = 'Corentin' le dernier avec cette clause :
Code : SQL
1
GROUP BY possesseur DESC

Les fonctions d'agrégat

Maintenant que nous savons créer ces agrégats, il serait bon de pouvoir en tirer profit. :D
Pour cela, il existe plusieurs fonctions que l'on peut utiliser sur chaque agrégat en présence d'une clause GROUP BY.
Notez que s'il n'y a pas de clause GROUP BY, ce sont toutes les entrées concernées qui sont considérées comme un grand agrégat.


Compter le nombre d'entrées par agrégat


Vous connaissez déjà cette fonction puisqu'il s'agit tout simplement de COUNT().
Cette fonction retourne le nombre de valeurs qui ne valent pas NULL dans la colonne du champ passé en argument dans l'agrégat.
Notez toutefois que COUNT(*) fait office d'exception car il ne fait que compter le nombre de lignes sans vérifier les valeurs.
Exemple : sélectionner le nombre de jeux proposés par chaque possesseur :
Code : SQL
1
SELECT possesseur, COUNT(id) AS nb_jeux FROM jeux_videos GROUP BY possesseur;

Qui retourne :
possesseurnb_jeux
Corentin 2
Florent 17
Mathieu 3
Michel 12
Patrick 13
Sébastien 1
Sebastien 2

Ce qui signifie qu'il y a deux id ne valant pas NULL dans l'agrégat possesseur = 'Corentin', et on peut avoir le même raisonnement avec les autres agrégats.

Le ne valant pas NULL peut sembler inutile, cependant il permet dans une requête avec jointure externe de donner 0 et non 1 si la jointure ne trouve aucune entrée dans la table jointe, car dans ce cas c'est la valeur NULL qui est sélectionnée pour les entrées de cette table.

On peut aussi utiliser DISTINCT pour faire un comptage distinct.
Par exemple, si on veut compter le nombre de consoles différentes de chaque possesseur, il faudra préciser qu'on veut les consoles distinctes de l'agrégat ainsi :
Code : SQL
1
SELECT possesseur, COUNT(DISTINCT console) AS nb_consoles FROM jeux_videos GROUP BY possesseur;

Ainsi la requête retourne :
possesseurnb_consoles
Corentin 2
Florent 11
Mathieu 3
Michel 7
Patrick 6
Sébastien 1
Sebastien 2

Pour vous exercer, vous pouvez essayer d'obtenir :


Faire la moyenne des valeurs d'une colonne par agrégat


Nous allons maintenant voir la fonction AVG() (de average, moyenne en anglais).
Elle retourne la moyenne des valeurs présentes dans la colonne du champ que l'on passe en argument parmi les lignes de l'agrégat.
Bien entendu, cela ne concerne que les champs numériques.

Exemple : on veut connaître le prix moyen des jeux par console.
Code : SQL
1
SELECT console, AVG(prix) AS prix_moyen FROM jeux_videos GROUP BY console;

Pour améliorer, même si ça n'a rien à voir avec le tuto, on peut arrondir le résultat à deux décimales avec ROUND(nombre, nombre_de_decimales) ainsi :
Code : SQL
1
SELECT console, ROUND(AVG(prix), 2) AS prix_moyen FROM jeux_videos GROUP BY console;

Cette requête retourne :
consoleprix_moyen
Dreamcast 12.67
Gameboy 5.00
Gamecube 47.50
GBA 34.50
Megadrive 9.00
NES 4.33
Nintendo 64 21.50
PC 36.33
PS 26.00
PS2 28.11
SuperNES 8.67
Xbox 48.30

Ce qui signifie que le prix moyen, qu'on a récupéré par AVG(prix), arrondi à 2 décimales est de 12.67 pour la Dreamcast.
Vérifions.
Faisons la somme des prix :
Code : SQL
1
SELECT prix FROM jeux_videos WHERE console = 'Dreamcast';

prix
12
11
15

12 + 11 + 15 = 23 + 15 = 38.
Il y a 3 entrées donc divisons par 3 : 38 / 3 ~= 12.67.
On retrouve bien la moyenne. :)

Tiens, nous venons de faire la somme...
Intéressant, ça. :)

Faire la somme des valeurs d'une colonne par agrégat


Eh bien il existe une fonction spéciale pour ça, il s'agit de SUM().
SUM() retourne donc la somme de toutes les valeurs présentes dans la colonne du champ passé en argument dans l'agrégat.

Exemple : on veut connaître le prix total que l'on devrait payer si l'on achetait tous les jeux d'un possesseur, par ce possesseur.
Il s'agit de la somme des prix.
Nous utiliserons donc la requête suivante :
Code : SQL
1
SELECT possesseur, SUM(prix) AS prix_total FROM jeux_videos GROUP BY possesseur;

Qui retourne :
possesseurprix_total
Corentin 34
Florent 393
Mathieu 65
Michel 367
Patrick 448
Sébastien 49
Sebastien 61

Donc, la somme de toutes les valeurs de la colonne du champ prix dans l'agrégat possesseur = 'Corentin' vaut 34. :)

Sélectionner la valeur minimale / maximale d'une colonne par agrégat


Nous allons utiliser les fonctions MIN() et MAX().
MIN() retourne la valeur minimale parmi celles de la colonne du champ passé en argument dans l'agrégat, MAX() sa valeur maximale.

Exemple : on veut connaître le prix du jeu le moins cher et celui du jeu le plus cher, selon la console.
On utilisera la requête suivante :
Code : SQL
1
SELECT console, MIN(prix) AS prix_minimal, MAX(prix) AS prix_maximal FROM jeux_videos GROUP BY console;

Qui retourne :
consoleprix_minimalprix_maximal
Dreamcast 11 15
Gameboy 5 5
Gamecube 40 55
GBA 25 44
Megadrive 2 15
NES 2 7
Nintendo 64 15 25
PC 15 50
PS 20 30
PS2 10 59
SuperNES 6 10
Xbox 29 60

Donc, la valeur minimale présente dans la colonne du champ prix de l'agrégat console = 'Dreamcast' est 11, sa valeur maximale est 15.

Concaténer les valeurs de la colonne d'un champ par agrégat


Nous allons ici faire de la concaténation.
Il s'agit de mettre bout à bout différentes chaînes ; ici, ce seront les valeurs présentes dans la colonne d'un champ par agrégat.
On utilisera la fonction GROUP_CONCAT(), qui s'utilisera ainsi :
Code : SQL
1
GROUP_CONCAT([DISTINCT] champ_ou_valeur[, champ_ou_valeur[, ...]] [ORDER BY champ] [SEPARATOR chaine])
Les crochets indiquent que leur contenu est facultatif


Cela évaluera d'abord la concaténation de tout ce qui est passé en argument par ligne, puis insérera le séparateur, qui peut être indiqué par SEPARATOR et qui est par défaut la virgule, entre les concaténations qui seront classées s'il est indiqué par le ORDER BY.
Si on indique DISTINCT, on ne gardera parmi les concaténations identiques que la première.

Un petit exemple serait le bienvenu pour la compréhension, je crois. :p

Exemple : on veut récupérer une chaîne de la forme Nom du jeu (prix?) pour chaque jeu, avec un retour à la ligne entre chacune de ces lignes, regroupé par console et les jeux doivent être classés par prix.
On utilisera la requête suivante :
Code : SQL
1
SELECT console, GROUP_CONCAT(nom, ' (', prix, '?)' ORDER BY prix SEPARATOR '\n') AS phrase FROM jeux_videos GROUP BY console;

Donc : on prend nom (prix?), on ordonne selon la valeur de prix dans la ligne de l'agrégat où l'on prend la concaténation, et on sépare par un retour à la ligne (\n) les concaténations obtenues.

Ce qui donne :
consolephrase
Dreamcast Crazy Taxi (11?)
NBA 2k (12?)
Sydney 2000 (15?)
Gameboy Tetris (5?)
Gamecube Wario World (40?)
Super Smash Bros Melee (55?)
GBA F-ZERO (25?)
Pokemon Rubis (44?)
Megadrive Sonic (2?)
Street Fighter 2 (10?)
Spider-Man (15?)
NES The Rocketeer (2?)
Super Mario Bros (4?)
Ice Hockey (7?)
Nintendo 64 Zelda : ocarina of time (15?)
Monopoly (21?)
FIFA 64 (25?)
Mario Kart 64 (25?)
etc... etc...

Pas mal, non ? :D

Les autres fonctions d'agrégats


Nous venons de voir les principales fonctions d'agrégat, c'est-à-dire les plus utilisées.
Il en existe toutefois quelques-unes que je n'ai pas présentées.
Au programme :

Si cela vous intéresse, je vous invite à consulter la page de la documentation réservée aux fonctions d'agrégats.

L'option WITH ROLLUP

GROUP BY peut s'utiliser avec l'option WITH ROLLUP ainsi :
Code : SQL
1
GROUP BY champ WITH ROLLUP

Cette option créera à la fin de chaque "groupe d'agrégats" une entrée valant NULL pour les champs et la valeur retournée par une fonction d'agrégat appliquée à tous les champs du "groupe d'agrégats" si une fonction d'agrégats est utilisée.
C'est-à-dire que si par exemple on voulait connaître, en plus du prix minimal et maximal par console, le prix minimal et maximal global, on utiliserait la même requête que celle vue pour MIN()/MAX() mais avec l'option WITH ROLLUP, qui rajoutera une entrée ainsi :

consoleprix_minimalprix_maximal
NULL 2 60

après les autres.
En cas de GROUP BY sur plusieurs champs, l'option WITH ROLLUP créera ce genre d'entrée à chaque fin de "sous-agrégat".
C'est-à-dire qu'en classant par possesseur puis par console, on aurait un ROLLUP pour chaque possesseur puis pour l'ensemble des entrées.

Notez que l'utilisation de WITH ROLLUP rend impossible celle d'une clause ORDER BY, bien qu'on puisse toujours indiquer l'ordre d'apparition des agrégats via la clause GROUP BY elle-même.

La clause HAVING

Je vais maintenant vous présenter la clause HAVING.
Celle-ci se situe juste après la clause GROUP BY et permet, tout comme la clause WHERE, de trier vos données pour ne prendre que celles qui vous intéressent.
La différence est que dans la clause WHERE vous ne pouvez pas utiliser d'alias définis dans le SELECT.
Exemple : pour chaque jeu, il y a 2 ? de frais divers qui s'ajoutent au prix indiqué, et on veut les prix T.T.C. des jeux pour lesquels ce dernier est inférieur ou égal à 30 ?.
On utilisera la clause HAVING ainsi :
Code : SQL
1
SELECT nom, console, prix + 2 AS prix_ttc FROM jeux_videos HAVING prix_ttc <= 30;

Cette requête retournera :
nomconsoleprix_ttc
Super Mario Bros NES 6
Sonic Megadrive 4
Zelda : ocarina of time Nintendo 64 17
Mario Kart 64 Nintendo 64 27
... ... ...

Alors qu'avec une clause WHERE, on vous aurait signalé que le champ prix_ttc n'existe pas.
On peut également utiliser la clause HAVING avec les fonctions d'agrégat, c'est d'ailleurs ce qui lui vaut sa place dans ce tutoriel. ^^
Ainsi, si on veut la liste des vendeurs vendant plus de cinq jeux, on utilisera cette requête :
Code : SQL
1
SELECT possesseur FROM jeux_videos GROUP BY possesseur HAVING COUNT(nom) > 5; -- On n'oublie pas le GROUP BY puisque l'on utilise une fonction d'agrégat

Qui retourne :
possesseur
Florent
Michel
Patrick

Bien entendu, on aurait pu également sélectionner le nombre de jeux et à ce moment-là, utiliser un alias dans la clause HAVING de cette manière :
Code : SQL
1
SELECT possesseur, COUNT(nom) AS nb_jeux FROM jeux_videos GROUP BY possesseur HAVING nb_jeux > 5;

Voilà, vous savez maintenant utiliser la clause GROUP BY.
Toutefois, je vous conseille de faire quelques requêtes de test en local pour être sûr d'avoir tout bien compris avant de l'utiliser dans vos scripts. ;)

Remercions Romain128 pour l'icône 50 x 50 pixels.
Si vous n'avez pas compris, c'est imagé.
En fait, les deux ronds du bas représentent deux entrées ayant id=1 qui se rejoignent dans l'agrégat id=1 qui est leur réunion, puisque du rouge dans du bleu donne du violet...
Si ça, c'est pas de l'art ! :)
Auteur : Dentuk
Noter et commenter ce tutoriel
Imprimer ce tutoriel

Changer de design | En savoir plus | Plan du site | Politique d'accessibilité | Règles | Fil RSS | XHTML 1.0 | CSS 2.0
Edité par Simple IT SARL : Nous contacter | Revue de presse | Publicité

Y'a plus rien à lire, faut remonter maintenant !

Vous souhaitez apparaître ici ? Contactez-nous.

Nombre de connectés 462 Zéros connectés | Requêtes SQL 7 requêtes | Temps de génération de la page : Total (SQL) 0.7062s (0.5814s)