Licence CC BY-NC-SA

Exercices sur les agrégats

Jusqu'à maintenant, tout a été très théorique. Or, la meilleure façon d'apprendre, c'est la pratique. Voici donc quelques exercices que je vous conseille de faire. S'il vaut mieux que vous essayiez par vous-mêmes avant de regarder la solution, ne restez cependant pas bloqués trop longtemps sur un exercice, et prenez toujours le temps de bien comprendre la solution.

Du simple…

1. Combien de races avons-nous dans la table Race ?

1
2
SELECT COUNT(*) 
FROM Race;

Simple échauffement.

2. De combien de chiens connaissons-nous le père ?

1
2
3
4
SELECT COUNT(pere_id)
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
WHERE Espece.nom_courant = 'Chien';

L'astuce ici était de ne pas oublier de donner la colonne pere_id en paramètre à COUNT(), pour ne compter que les lignes où pere_id est non NULL. Si vous avez fait directement WHERE espece_id = 1 au lieu d'utiliser une jointure pour sélectionner les chiens, ce n'est pas bien grave.

3. Quelle est la date de naissance de notre plus jeune femelle ?

1
2
3
SELECT MAX(date_naissance) 
FROM Animal
WHERE sexe = 'F';

4. En moyenne, quel est le prix d'un chien ou d'un chat de race, par espèce, et en général ?

1
2
3
4
5
SELECT nom_courant AS Espece, AVG(Race.prix) AS prix_moyen
FROM Race
INNER JOIN Espece ON Race.espece_id = Espece.id
WHERE Espece.nom_courant IN ('Chat', 'Chien')
GROUP BY Espece.nom_courant WITH ROLLUP;

Ne pas oublier WITH ROLLUP pour avoir le résultat général.

5. Combien avons-nous de perroquets mâles et femelles, et quels sont leurs noms (en une seule requête bien sûr) ?

1
2
3
4
5
SELECT sexe, COUNT(*), GROUP_CONCAT(nom SEPARATOR ', ')
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
WHERE nom_courant = 'Perroquet amazone'
GROUP BY sexe;

Il suffisait de se souvenir de la méthode GROUP_CONCAT() pour pouvoir réaliser simplement cette requête. Peut-être avez-vous groupé sur l'espèce aussi (avec nom_courant ou autre). Ce n'était pas nécessaire puisqu'on avait restreint à une seule espèce avec la clause WHERE. Cependant, cela n'influe pas sur le résultat, mais sur la rapidité de la requête.

…Vers le complexe

1. Quelles sont les races dont nous ne possédons aucun individu ?

1
2
3
4
5
SELECT Race.nom, COUNT(Animal.race_id) AS nombre
FROM Race
LEFT JOIN Animal ON Animal.race_id = Race.id
GROUP BY Race.nom
HAVING nombre = 0;

Il fallait ici ne pas oublier de faire une jointure externe (LEFT ou RIGHT, selon votre requête), ainsi que de mettre la colonne Animal.race_id (ou Animal.id, ou Animal.espece_id mais c'est moins intuitif) en paramètre de la fonction COUNT().

2. Quelles sont les espèces (triées par ordre alphabétique du nom latin) dont nous possédons moins de cinq mâles ?

1
2
3
4
5
6
SELECT Espece.nom_latin, COUNT(espece_id) AS nombre
FROM Espece
LEFT JOIN Animal ON Animal.espece_id = Espece.id
WHERE sexe = 'M' OR Animal.id IS NULL
GROUP BY Espece.nom_latin
HAVING nombre < 5;

À nouveau, une jointure externe et espece_id en argument de COUNT(), mais il y avait ici une petite subtilité en plus. Puisqu'on demandait des informations sur les mâles uniquement, il fallait une condition WHERE sexe = 'M'. Mais cette condition fait que les lignes de la jointure provenant de la table Espece n'ayant aucune correspondance dans la table Animal sont éliminées également (puisque forcément, toutes les colonnes de la table Animal, dont sexe, seront à NULL pour ces lignes). Par conséquent, il fallait ajouter une condition permettant de garder ces fameuses lignes (les espèces pour lesquelles on n'a aucun individu, donc aucun mâle). Il fallait donc ajouter OR Animal.id IS NULL, ou faire cette condition sur toute autre colonne d'Animal ayant la contrainte NOT NULL, et qui donc ne sera NULL que lors d'une jointure externe, en cas de non-correspondance avec l'autre table. Il n'y a plus alors qu'à ajouter la clause HAVING pour sélectionner les espèces ayant moins de cinq mâles.

3. Combien de mâles et de femelles de chaque race avons-nous, avec un compte total intermédiaire pour les races (mâles et femelles confondues) et pour les espèces ? Afficher le nom de la race, et le nom courant de l'espèce.

1
2
3
4
5
6
SELECT Animal.sexe, Race.nom, Espece.nom_courant, COUNT(*) AS nombre
FROM Animal
INNER JOIN Espece ON Animal.espece_id = Espece.id
INNER JOIN Race ON Animal.race_id = Race.id
WHERE Animal.sexe IS NOT NULL
GROUP BY Espece.nom_courant, Race.nom, sexe WITH ROLLUP;

Deux jointures sont nécessaires pour pouvoir afficher les noms des races et des espèces. Il suffit alors de ne pas oublier l'option WITH ROLLUP et de mettre les critères de regroupement dans le bon ordre pour avoir les super-agrégats voulus.

4. Quel serait le coût, par espèce et au total, de l'adoption de Parlotte, Spoutnik, Caribou, Cartouche, Cali, Canaille, Yoda, Zambo et Lulla ?

Petit indice, pour avoir le prix d'un animal selon que sa race soit définie ou non, vous pouvez utiliser une fonction que nous venons de voir au chapitre précédent.

1
2
3
4
5
6
SELECT Espece.nom_courant, SUM(COALESCE(Race.prix, Espece.prix)) AS somme
FROM Animal
INNER JOIN Espece ON Espece.id = Animal.espece_id
LEFT JOIN Race ON Race.id = Animal.race_id
WHERE Animal.nom IN ('Parlotte', 'Spoutnik', 'Caribou', 'Cartouche', 'Cali', 'Canaille', 'Yoda', 'Zambo', 'Lulla')
GROUP BY Espece.nom_courant WITH ROLLUP;

C'est ici la fonction SUM() qu'il fallait utiliser, puisqu'on veut le prix total par groupe. Sans oublier le WITH ROLLUP pour avoir également le prix total tous groupes confondus. Quant au prix de chaque animal, c'est typiquement une situation où l'on peut utiliser COALESCE() !