Comment obtenez-vous les lignes qui contiennent la valeur maximale pour chaque ensemble groupé?
J'ai vu des variations trop compliquées sur cette question, et aucune avec une bonne réponse. J'ai essayé de rassembler l'exemple le plus simple possible:
Étant donné un tableau comme celui-ci ci-dessous, avec les colonnes personne, groupe et âge, comment obtiendriez-vous la personne la plus âgée dans chaque groupe? (Une égalité au sein d'un groupe devrait donner le premier résultat alphabétique)
Person | Group | Age
---
Bob | 1 | 32
Jill | 1 | 34
Shawn| 1 | 42
Jake | 2 | 29
Paul | 2 | 36
Laura| 2 | 39
Ensemble de résultats souhaité:
Shawn | 1 | 42
Laura | 2 | 39
Réponses:
Il y a un moyen super simple de le faire dans mysql:
Cela fonctionne parce que dans mysql, vous êtes autorisé à ne pas agréger les colonnes non groupées, auquel cas mysql renvoie simplement la première ligne. La solution consiste à d'abord classer les données de telle sorte que pour chaque groupe, la ligne souhaitée soit la première, puis à regrouper en fonction des colonnes pour lesquelles vous souhaitez obtenir la valeur.
Vous évitez les sous-requêtes compliquées qui tentent de trouver
max()
etc., ainsi que les problèmes de renvoi de plusieurs lignes lorsqu'il y en a plusieurs avec la même valeur maximale (comme le feraient les autres réponses)Remarque: il s'agit d'une solution réservée à mysql . Toutes les autres bases de données que je connais lèveront une erreur de syntaxe SQL avec le message "les colonnes non agrégées ne sont pas répertoriées dans la clause group by" ou similaire. Parce que cette solution utilise sans papier comportement, plus prudent peut vouloir inclure un test pour affirmer qu'il reste à travailler si une version future de MySQL changer ce comportement.
Mise à jour de la version 5.7:
Depuis la version 5.7, le
sql-mode
paramètre inclutONLY_FULL_GROUP_BY
par défaut, donc pour que cela fonctionne, vous ne devez pas avoir cette option (modifiez le fichier d'options du serveur pour supprimer ce paramètre).la source
SELECT
clause et n'est pas calculée à l'aide d'une fonction d'agrégation.SELECT
clause ne dépendent pas fonctionnellement desGROUP BY
colonnes. S'il est configuré pour l'accepter (`ONLY_FULL_GROUP_BY` est désactivé), il fonctionne comme les versions précédentes (c'est-à-dire que les valeurs de ces colonnes sont indéterminées).GROUP BY
condensé en un seul enregistrement, mais tous les champs seront arbitrairement choisis dans les enregistrements. Il se peut que MySQL sélectionne toujours simplement la première ligne, mais il pourrait tout aussi bien choisir n'importe quelle autre ligne ou même des valeurs de différentes lignes dans une future version.La bonne solution est:
Comment ça fonctionne:
Il fait correspondre chaque ligne de
o
avec toutes les lignesb
ayant la même valeur dans la colonneGroup
et une plus grande valeur dans la colonneAge
. Toute ligneo
ne contenant pas la valeur maximale de son groupe dans la colonneAge
correspondra à une ou plusieurs lignes deb
.Le
LEFT JOIN
fait correspondre la personne la plus âgée du groupe (y compris les personnes seules dans leur groupe) avec une rangée pleine deNULL
s deb
(«pas de plus grand âge dans le groupe»).L'utilisation
INNER JOIN
rend ces lignes non identiques et elles sont ignorées.La
WHERE
clause ne conserve que les lignes ayantNULL
s dans les champs extraits deb
. Ce sont les personnes les plus âgées de chaque groupe.Lectures complémentaires
Cette solution et bien d'autres sont expliquées dans le livre SQL Antipatterns: éviter les pièges de la programmation de base de données
la source
o.Age = b.Age
, par exemple si Paul du groupe 2 est sur 39 comme Laura. Cependant, si nous ne voulons pas d'un tel comportement, nous pouvons le faire:ON o.Group = b.Group AND (o.Age < b.Age or (o.Age = b.Age and o.id < b.id))
Vous pouvez vous joindre à une sous-requête qui tire le
MAX(Group)
etAge
. Cette méthode est portable sur la plupart des SGBDR.la source
Group = 2, Age = 20
, la sous-requête renverrait l'une d'entre elles, mais laON
clause de jointure correspondrait aux deux , donc vous obtiendrez 2 lignes avec le même groupe / âge, bien que des valeurs différentes pour les autres colonnes, plutôt qu'un.Ma solution simple pour SQLite (et probablement MySQL):
Cependant, cela ne fonctionne pas dans PostgreSQL et peut-être sur d'autres plateformes.
Dans PostgreSQL, vous pouvez utiliser la clause DISTINCT ON :
la source
Utilisation de la méthode de classement.
la source
:=
auparavant - qu'est-ce que c'est?Je ne sais pas si MySQL a la fonction row_number. Si c'est le cas, vous pouvez l'utiliser pour obtenir le résultat souhaité. Sur SQL Server, vous pouvez faire quelque chose de similaire à:
la source
La solution d'Axiac est finalement celle qui a le mieux fonctionné pour moi. J'avais cependant une complexité supplémentaire: une "valeur max" calculée, dérivée de deux colonnes.
Prenons le même exemple: je voudrais la personne la plus âgée de chaque groupe. S'il y a des gens qui sont tout aussi âgés, prenez la personne la plus grande.
J'ai dû effectuer la jointure gauche deux fois pour obtenir ce comportement:
J'espère que cela t'aides! Je suppose qu'il devrait y avoir une meilleure façon de le faire ...
la source
Ma solution ne fonctionne que si vous avez besoin de récupérer une seule colonne, mais pour mes besoins était la meilleure solution trouvée en termes de performances (elle n'utilise qu'une seule requête!):
Il utilise GROUP_CONCAT afin de créer une liste de concaturation ordonnée puis je sous-chaîne à la première seulement.
la source
J'ai une solution simple en utilisant
WHERE IN
la source
Utilisation des CTE - Expressions de table communes:
la source
Dans Oracle ci-dessous, la requête peut donner le résultat souhaité.
la source
la source
Vous pouvez aussi essayer
la source
Je n'utiliserais pas Group comme nom de colonne car c'est un mot réservé. Cependant, suivre SQL fonctionnerait.
la source
Cette méthode a l'avantage de vous permettre de classer par une colonne différente et de ne pas jeter les autres données. C'est très utile dans une situation où vous essayez de répertorier les commandes avec une colonne d'articles, en répertoriant les plus lourdes en premier.
Source: http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
la source
que le nom de la table soit des personnes
la source
Si l'ID (et tous les coulmns) est nécessaire à partir de mytable
la source
Voici comment j'obtiens les N max lignes par groupe dans mysql
Comment ça fonctionne:
co.country = ci.country
) < 1
donc pour 3 éléments -) <3co.id < ci.id
Exemple complet ici:
mysql sélectionner n valeurs max par groupe
la source