27th Apr 2022 11 minutes de lecture Introduction à l'utilisation des fonctions agrégées SQL avec les JOINs Francisco Claria Fonctions d’agrégation join bases du sql Table des matières Que font les fonctions d'agrégation SQL ? JOINs parent-enfant Agrégat + GROUP BY + JOIN MIN + GROUP BY + JOIN SOMME + GROUP BY + JOIN COUNT + GROUP BY + JOIN MOYENNE + GROUP BY + JOIN MOYENNE + GROUP BY + JOINTURES Filtrage des résultats Utilisation du prédicat JOIN Utilisation des conditions WHERE Utilisation des conditions HAVING Gérer les NULLs Un dernier conseil pour travailler avec les fonctions agrégées SQL Précédemment, nous avons discuté de la méthode utilisation des fonctions d'agrégation SQL avec l'instruction GROUP BY. Les lecteurs assidus de notre blog se souviendront également de notre récent article sur l'utilisation de . tutoriel sur les JOINs. Si vous êtes un peu rouillé sur l'un ou l'autre sujet, je vous encourage à les revoir avant de poursuivre cet article. En effet, nous allons approfondir le sujet. fonctions d'agrégation en les associant à des JOIN. Ce duo libère toutes les possibilités des fonctions agrégées SQL et nous permet d'effectuer des calculs sur plusieurs tables dans une seule requête. Que font les fonctions d'agrégation SQL ? Voici une présentation rapide des fonctions agrégées SQL les plus courantes : FUNCTIONPURPOSEEXAMPLE MIN Returns the smallest value in a column. SELECT MIN(column) FROM table_name MAX Returns the largest value in a column SELECT MAX(column) FROM table_name SUM Calculates the sum of all numeric values in a column SELECT SUM(column) FROM table_name AVG Returns the average value for a column SELECT AVG(column) FROM table_name COUNT(column) Counts the number of non-null values in a column SELECT COUNT(column) FROM table_name COUNT(*) Counts the total number of rows (including NULLs) in a column SELECT COUNT(*) FROM table_name Il est également important de se rappeler que l'instruction GROUP BY, lorsqu'elle est utilisée avec des agrégats, calcule des valeurs qui ont été regroupées par colonne. (Pour plus d'informations, voir Guide des fonctions d'agrégation SQL pour les débutants.) Nous pouvons utiliser GROUP BY avec l'une des fonctions ci-dessus. Par exemple, nous utilisons la fonction MIN() dans l'exemple ci-dessous : SELECT MIN(column_name) FROM table_name GROUP BY group_column Cela permettrait de récupérer la valeur minimale trouvée dans column_name pour chaque ensemble de valeurs dans un groupe basé sur la colonne group_column. La même idée s'applique aux fonctions MAX, SUM, AVG et COUNT. JOINs parent-enfant Examinons maintenant quelques situations courantes dans lesquelles vous utiliserez des JOIN de groupe avec des fonctions d'agrégation. Si vous avez lu A Beginner's Guide to SQL Aggregate Functions, le schéma suivant vous sera déjà familier : Si vous avez déjà utilisé ce modèle auparavant (par exemple en faisant les exemples de l'article précédent), veillez à effacer tous les enregistrements existants de votre table. Vous pouvez le faire en exécutant les commandes suivantes : TRUNCATE cities; TRUNCATE users; Entrons de nouvelles données dans les tables : INSERT INTO `cities` VALUES (1,'Miami'), (2,'Orlando'), (3,'Las Vegas'), (4,'Coyote Springs'); INSERT INTO `users` VALUES (1,1,'John','Doe',22), (2,1,'Albert','Thomson',15), (3,2,'Robert','Ford',65), (4,3,'Samantha','Simpson',9), (5,2,'Carlos','Bennet',42), (6,2,'Mirtha','Lebrand',81), (7,3,'Alex','Gomez',31); Nous avons donc une table appelée users et une autre table appelée cities. Ces deux tables ont quelque chose en commun : une valeur numérique d'identification de la ville. Cette valeur est stockée dans la colonne id de la table cities et dans la colonne city_id de la table users de la table . La colonne city_id contient une référence (aussi appelée clé étrangère) qui relie un enregistrement d'utilisateur à une ville. Ces enregistrements correspondants nous permettent d'associer les deux tables à JOIN. En d'autres termes, nous connaissons la ville d'un utilisateur lorsque nous récupérons l'enregistrement de la table cities qui a une valeur id égale à la valeur dans users.city_id. Dans la requête suivante, nous pouvons voir cela en action : SELECT cities.*, users.* FROM cities JOIN users ON cities.id = users.city_id; cities  users cityname id city_id id first_name last_name age Miami 1 1 1 John Doe 22 Miami 1 1 2 Albert Thomson 15 Orlando 2 2 3 Robert Ford 65 Las Vegas 3 3 4 Samantha Simpson 9 Orlando 2 2 5 Carlos Bennet 42 Orlando 2 2 6 Mirtha Lebrand 81 Las Vegas 3 3 7 Alex Gomez 31 Puisque la table users est connectée à une ville via la clé étrangère city_id, nous pouvons dire qu'un utilisateur appartient à une ville et que la ville a donc de nombreux utilisateurs. Il s'agit d'une relation parent-enfant (villes-utilisateurs) ; la table users partage un lien avec la table cities table. Avec cette relation en tête, passons à autre chose et voyons comment nous pouvons calculer des données résumées intéressantes qui relient les deux tables. Agrégat + GROUP BY + JOIN Commençons maintenant à aborder des situations pratiques où nous devrons GROUPdes valeurs provenant de JOINdes tables éditées. MIN + GROUP BY + JOIN Le calcul de valeurs basées sur des enregistrements enfants qui sont regroupés par une colonne parent est assez courant. Construisons une requête qui récupérera le plus petit users.age (enregistrement enfant) pour chaque cityname (enregistrement parent) : SELECT cities.cityname, MIN(users.age) FROM cities JOIN users ON cities.id = users.city_id GROUP BY cities.cityname Cette requête renverra : cityname MIN(users.age) Las Vegas 9 Miami 15 Orlando 42 Il y a quelque chose de très important à souligner sur la façon dont le JOIN fonctionne. Ce sera plus évident si nous examinons toutes les villes : SELECT cities.cityname FROM cities cityname Coyote Springs Las Vegas Miami Orlando Comme vous pouvez le voir, "Coyote Springs" n'était pas listée avant parce qu'elle n'a pas d'utilisateurs. Si vous vouliez que cette ville soit listée dans les résultats résumés, vous devriez utiliser la commande LEFT JOIN à la place : SELECT cities.cityname, MIN(users.age) FROM cities LEFT JOIN users ON cities.id = users.city_id GROUP BY cities.cityname Ceci retournera : cityname MIN(users.age) Coyote Springs null Las Vegas 9 Miami 15 Orlando 42 Que cela ait du sens ou non dépendra de votre cas d'utilisation, mais il est important que vous gardiez cette situation à l'esprit lorsque vous joignez des tables. MAX + GROUP BY + JOINTURES Nous pouvons trouver l'âge le plus élevé pour chaque ville en utilisant la fonction MAX(): SELECT cities.cityname, MAX(users.age) FROM cities LEFT JOIN users ON cities.id = users.city_id GROUP BY cities.cityname La requête ci-dessus permettra de récupérer : cityname MAX(users.age) Coyote Springs null Las Vegas 31 Miami 22 Orlando 81 Notez que j'ai utilisé LEFT JOIN. Je veux une liste de toutes les villes, pas seulement celles auxquelles sont associés des enregistrements d'utilisateurs. SOMME + GROUP BY + JOIN Voyons maintenant comment totaliser les âges pour chaque ville. Nous pouvons utiliser la fonction SUM() pour ce faire : SELECT cities.cityname, SUM(users.age) FROM cities LEFT JOIN users ON cities.id = users.city_id GROUP BY cities.cityname Qui renvoie : cityname SUM(users.age) Coyote Springs null Las Vegas 40 Miami 37 Orlando 188 COUNT + GROUP BY + JOIN Supposons que nous voulions voir le nombre d'utilisateurs dans chaque ville. Nous utiliserions la fonction COUNT(), comme ceci : SELECT cities.cityname, COUNT(users.id) FROM cities LEFT JOIN users ON cities.id = users.city_id GROUP BY cities.cityname Qui renvoie : cityname COUNT(users.id) Coyote Springs 0 Las Vegas 2 Miami 2 Orlando 3 MOYENNE + GROUP BY + JOIN En utilisant le nombre d'utilisateurs dans chaque ville (COUNT) et le SUM des âges combinés des utilisateurs de chaque ville, nous pouvons calculer l'âge moyen de chaque ville. Il suffit de diviser la somme des âges par le nombre d'utilisateurs de chaque ville : SELECT cities.cityname, SUM(users.age) AS sum, COUNT(users.id) AS count, SUM(users.age) / COUNT(users.id) AS average FROM cities LEFT JOIN users ON cities.id = users.city_id GROUP BY cities.cityname Retourner : cityname sum count average Coyote Springs null 0 null Las Vegas 40 2 20.0000 Miami 37 2 18.5000 Orlando 188 3 62.6667 Remarquez que la somme et la moyenne calculée donnent une valeur NULL pour Coyote Springs. Cela est dû au fait que Coyote Springs n'a pas d'utilisateurs et que la colonne résumée ne peut donc pas calculer de valeur numérique. MOYENNE + GROUP BY + JOINTURES L'exemple précédent utilisait un calcul que nous avons saisi pour trouver un âge moyen pour chaque ville. Nous aurions pu utiliser la fonction AVG() à la place, comme indiqué ci-dessous : SELECT cities.cityname, AVG(users.age) FROM cities LEFT JOIN users ON cities.id = users.city_id GROUP BY cities.cityname On obtient ainsi les mêmes valeurs que dans l'exemple précédent : cityname AVG(users.age) Coyote Springs null Las Vegas 20.0000 Miami 18.5000 Orlando 62.6667 Filtrage des résultats Vous aurez parfois besoin de filtrer les lignes en fonction de certaines conditions. Dans ce type de requête, il y a trois étapes où vous pouvez le faire : WHERE, HAVING, et JOIN. Selon la situation, chacune de ces options peut avoir un résultat différent. Il est important de comprendre laquelle utiliser lorsque vous souhaitez obtenir un résultat spécifique. Prenons quelques exemples pour illustrer ce point. Utilisation du prédicat JOIN Obtenons le nombre d'utilisateurs de moins de 30 ans dans chaque ville. Nous allons utiliser LEFT JOIN pour récupérer les villes sans aucun enregistrement d'utilisateur : SELECT cityname, COUNT(users.id) FROM cities LEFT JOIN users ON cities.id = users.city_id AND users.age < 30 GROUP BY cities.cityname ORDER BY cities.cityname; La condition permettant d'inclure uniquement les utilisateurs dont l'âge est inférieur à 30 ans est définie dans le prédicat JOIN. Le résultat est le suivant : cityname COUNT(users.id) Coyote Springs 0 Las Vegas 1 Miami 2 Orlando 0 Toutes les villes sont listées, et seuls les utilisateurs dont l'âge est compris dans la fourchette renvoient un nombre non nul. Les villes dont aucun utilisateur ne correspond à nos critères renvoient un zéro. Que se serait-il passé si nous avions placé la même condition de filtrage dans la clause WHERE? Utilisation des conditions WHERE Si vous placez les mêmes conditions dans la clause WHERE, vous obtiendrez le résultat suivant : SELECT cityname, COUNT(users.id) FROM cities LEFT JOIN users ON cities.id = users.city_id WHERE users.age < 30 GROUP BY cities.cityname ORDER BY cities.cityname; Le résultat est le suivant : cityname COUNT(users.id) Las Vegas 1 Miami 2 Ce n'est pas ce que j'attendais ; je voulais obtenir TOUTES les villes et le nombre de leurs utilisateurs respectifs âgés de moins de 30 ans. Même si une ville n'avait aucun utilisateur, elle aurait dû être répertoriée avec un nombre nul, comme le renvoie l'exemple de prédicat JOIN. La raison pour laquelle cela ne renvoie pas ces enregistrements est que WHERE sont appliquées après la condition JOIN. Puisque la condition users.age < 30 supprime tous les enregistrements "Coyote Springs" et "Orlando", le calcul résumé ne peut pas inclure ces valeurs. Seuls "Las Vegas" et "Miami" remplissent les conditions de WHERE, donc seuls "Las Vegas" et "Miami" sont renvoyés. En revanche, lorsque la condition est appliquée dans le prédicat JOIN, les enregistrements d'utilisateurs dont l'âge ne correspond pas sont supprimés avant que les deux tables ne soient jointes. Ensuite, toutes les villes sont mises en correspondance par les colonnes des utilisateurs, comme on peut s'y attendre lorsqu'on utilise un prédicat . LEFT JOIN. Cela signifie que toutes les villes feront partie des résultats ; seuls les enregistrements d'utilisateurs qui ne remplissent pas la condition users.age < 30 sont filtrés. Dans ce cas, le prédicat JOIN renvoie le résultat souhaité. Utilisation des conditions HAVING Nous l'avons mentionné dans le premier article, mais nous allons le répéter ici : l'utilisation de la clause WHERE pour filtrer des colonnes résumées ne fonctionne pas. Regardez l'exemple ci-dessous. SELECT cityname, COUNT(users.id) FROM cities LEFT JOIN users ON cities.id = users.city_id WHERE COUNT(users.id) > 2 GROUP BY cities.cityname ORDER BY cities.cityname; La base de données émet alors une plainte comme celle-ci de la part de MySQL : Error Code: 1111. Invalid use of group function Utilisez plutôt la clause HAVING: SELECT cityname, COUNT(users.id) FROM cities LEFT JOIN users ON cities.id = users.city_id GROUP BY cities.cityname HAVING COUNT(users.id) > 2 ORDER BY cities.cityname; Cela renvoie les enregistrements voulus (uniquement les villes ayant plus de deux utilisateurs) : cityname COUNT(users.id) Orlando 3 Gérer les NULLs Outre les cas limites déjà présentés, il est important de prendre en compte un élément qui n'est pas si évident. Revenons à l'exemple de COUNT(): SELECT cities.cityname, COUNT(users.id) FROM cities LEFT JOIN users ON cities.id = users.city_id GROUP BY cities.cityname Il renvoie : cityname COUNT(users.id) Coyote Springs 0 Las Vegas 2 Miami 2 Orlando 3 Si j'avais utilisé COUNT(*) au lieu de COUNT(users.id), le nombre total de lignes aurait été généré. Cela nous aurait donné une valeur non intentionnelle - dans ce cas, un faux "1" pour "Coyote Springs". Ce résultat est dû à la nature de LEFT JOIN, dont voici un exemple: SELECT cities.cityname, COUNT(*) FROM cities LEFT JOIN users ON cities.id = users.city_id GROUP BY cities.cityname Cela renverrait : cityname COUNT(users.id) Coyote Springs 1 Las Vegas 2 Miami 2 Orlando 3 Donc COUNT(*) compte un "1" pour Coyote Springs parce que LEFT JOIN renvoie une ligne avec des valeurs NULL. Rappelez-vous que dans COUNT(*), une ligne avec des valeurs NULL compte toujours. Pour la même raison, COUNT(users.id) renvoie le compte attendu de "0" ; la valeur de la colonne users.id est nulle pour Coyote Springs. En d'autres termes, utilisez toujours Count(column) avec ce type de requête. Un dernier conseil pour travailler avec les fonctions agrégées SQL Enfin, j'aimerais ajouter que l'utilisation des fonctions d'agrégation SQL- en particulier lorsqu'on utilise JOIN- exige que vous compreniez le langage SQL et les données avec lesquelles vous travaillez. Essayez d'abord les requêtes dans un sous-ensemble plus petit de vos données pour confirmer que tous les calculs fonctionnent comme prévu. Si possible, comparez certaines sorties à une valeur de référence pour valider les résultats de vos requêtes. N'oubliez pas que l'utilisation de conditions dans le prédicat JOIN (après ON) n'est pas la même chose que le filtrage dans WHERE (ou l'utilisation de HAVING). Cela peut créer des différences subtiles (ou pas si subtiles) dans vos données résumées, ce qui pourrait entraîner des erreurs difficiles à détecter. Portez une attention particulière à vos choix de filtrage. Comme toujours, merci de votre lecture et n'hésitez pas à partager vos propres expériences dans la section des commentaires. Tags: Fonctions d’agrégation join bases du sql