Il y a un tableau messages
qui contient les données comme indiqué ci-dessous:
Id Name Other_Columns
-------------------------
1 A A_data_1
2 A A_data_2
3 A A_data_3
4 B B_data_1
5 B B_data_2
6 C C_data_1
Si j'exécute une requête select * from messages group by name
, j'obtiendrai le résultat sous la forme:
1 A A_data_1
4 B B_data_1
6 C C_data_1
Quelle requête retournera le résultat suivant?
3 A A_data_3
5 B B_data_2
6 C C_data_1
Autrement dit, le dernier enregistrement de chaque groupe doit être renvoyé.
À l'heure actuelle, c'est la requête que j'utilise:
SELECT
*
FROM (SELECT
*
FROM messages
ORDER BY id DESC) AS x
GROUP BY name
Mais cela semble très inefficace. Y a-t-il d'autres façons d'obtenir le même résultat?
sql
mysql
group-by
greatest-n-per-group
Vijay Dev
la source
la source
Réponses:
MySQL 8.0 prend désormais en charge les fonctions de fenêtrage, comme presque toutes les implémentations SQL populaires. Avec cette syntaxe standard, nous pouvons écrire des requêtes avec le plus grand nombre n par groupe:
Voici la réponse originale que j'ai écrite pour cette question en 2009:
J'écris la solution de cette façon:
Concernant les performances, l'une ou l'autre solution peut être meilleure, selon la nature de vos données. Vous devez donc tester les deux requêtes et utiliser celle qui offre les meilleures performances compte tenu de votre base de données.
Par exemple, j'ai une copie du vidage de données StackOverflow August . Je vais l'utiliser pour l'analyse comparative. Il y a 1 114 357 lignes dans le
Posts
tableau. Cela fonctionne sur MySQL 5.0.75 sur mon Macbook Pro 2.40GHz.J'écrirai une requête pour trouver le message le plus récent pour un ID utilisateur donné (le mien).
En utilisant d'abord la technique montrée par @Eric avec le
GROUP BY
dans une sous-requête:Même l'
EXPLAIN
analyse prend plus de 16 secondes:Produisez maintenant le même résultat de requête en utilisant ma technique avec
LEFT JOIN
:L'
EXPLAIN
analyse montre que les deux tables peuvent utiliser leurs index:Voici le DDL pour ma
Posts
table:la source
<=
n'aidera pas si vous avez une colonne non unique. Vous devez utiliser une colonne unique comme bris d'égalité.UPD: 2017-03-31, la version 5.7.5 de MySQL a rendu le commutateur ONLY_FULL_GROUP_BY activé par défaut (par conséquent, les requêtes GROUP BY non déterministes ont été désactivées). De plus, ils ont mis à jour l'implémentation GROUP BY et la solution pourrait ne plus fonctionner comme prévu, même avec le commutateur désactivé. Il faut vérifier.
La solution de Bill Karwin ci-dessus fonctionne bien lorsque le nombre d'éléments au sein des groupes est plutôt faible, mais les performances de la requête deviennent mauvaises lorsque les groupes sont assez grands, car la solution ne nécessite
n*n/2 + n/2
que desIS NULL
comparaisons.J'ai fait mes tests sur une table InnoDB de
18684446
lignes avec des1182
groupes. Le tableau contient les résultats des tests pour les tests fonctionnels et a la(test_id, request_id)
clé primaire comme. Ainsi,test_id
est un groupe et je cherchais le dernierrequest_id
pour chacuntest_id
.La solution de Bill fonctionne déjà depuis plusieurs heures sur mon dell e4310 et je ne sais pas quand elle va se terminer même si elle fonctionne sur un indice de couverture (donc
using index
dans EXPLAIN).J'ai quelques autres solutions basées sur les mêmes idées:
(group_id, item_value)
paire la plus grande est la dernière valeur à l'intérieur de chacungroup_id
, c'est la première pour chacungroup_id
si nous parcourons l'index dans l'ordre décroissant;3 façons dont MySQL utilise les index est un excellent article pour comprendre certains détails.
Solution 1
Celui-ci est incroyablement rapide, il faut environ 0,8 secondes sur mes 18 millions de lignes:
Si vous souhaitez modifier l'ordre en ASC, placez-le dans une sous-requête, renvoyez uniquement les identifiants et utilisez-le comme sous-requête pour joindre le reste des colonnes:
Celui-ci prend environ 1,2 secondes sur mes données.
Solution 2
Voici une autre solution qui prend environ 19 secondes pour ma table:
Il renvoie également les tests dans l'ordre décroissant. Il est beaucoup plus lent car il effectue un balayage d'index complet, mais il est là pour vous donner une idée de la sortie de N max de lignes pour chaque groupe.
L'inconvénient de la requête est que son résultat ne peut pas être mis en cache par le cache de requête.
la source
SELECT test_id, request_id FROM testresults GROUP BY test_id;
retournerait le request_id minimum pour chaque test_id.Utilisez votre sous - requête pour renvoyer le groupe correct, car vous êtes à mi-chemin.
Essaye ça:
Si ce n'est pas le cas,
id
vous voulez le maximum de:De cette façon, vous évitez les sous-requêtes corrélées et / ou l'ordre dans vos sous-requêtes, qui ont tendance à être très lentes / inefficaces.
la source
other_col
: si cette colonne n'est pas unique, vous pouvez récupérer plusieurs enregistrements avec le mêmename
, s'ils sont liésmax(other_col)
. J'ai trouvé cet article qui décrit une solution à mes besoins, où j'ai besoin exactement d'un enregistrement parname
.INDEX(name, id)
etINDEX(name, other_col)
Je suis arrivé à une solution différente, qui consiste à obtenir les ID du dernier message dans chaque groupe, puis à sélectionner dans le tableau des messages en utilisant le résultat de la première requête comme argument pour une
WHERE x IN
construction:Je ne sais pas comment cela fonctionne par rapport à certaines des autres solutions, mais cela a fonctionné de manière spectaculaire pour ma table avec plus de 3 millions de lignes. (Exécution de 4 secondes avec plus de 1200 résultats)
Cela devrait fonctionner à la fois sur MySQL et SQL Server.
la source
Solution par sous-requête violon Lien
Solution En joignant condition fiddle link
La raison de ce message est de donner un lien violon uniquement. Le même SQL est déjà fourni dans d'autres réponses.
la source
Une approche avec une vitesse considérable est la suivante.
Résultat
la source
id
que vous l'ordonniez comme vous en avez besoin. Dans le cas général, une autre colonne est nécessaire.Voici deux suggestions. Tout d'abord, si mysql prend en charge ROW_NUMBER (), c'est très simple:
Je suppose que par "dernier" vous voulez dire le dernier dans l'ordre d'identification. Sinon, modifiez la clause ORDER BY de la fenêtre ROW_NUMBER () en conséquence. Si ROW_NUMBER () n'est pas disponible, voici une autre solution:
Deuxièmement, si ce n'est pas le cas, c'est souvent une bonne façon de procéder:
En d'autres termes, sélectionnez les messages où il n'y a pas de message d'identification ultérieure portant le même nom.
la source
ROW_NUMBER()
et les CTE.Je n'ai pas encore testé avec une grande base de données mais je pense que cela pourrait être plus rapide que de rejoindre des tables:
la source
Voici une autre façon d'obtenir le dernier enregistrement associé en utilisant
GROUP_CONCAT
avec ordre par etSUBSTRING_INDEX
de choisir l'un des enregistrements dans la listeLa requête ci-dessus regroupera tous les éléments
Other_Columns
du mêmeName
groupe et l'utilisationORDER BY id DESC
joindra tous les élémentsOther_Columns
d'un groupe spécifique dans l'ordre décroissant avec le séparateur fourni dans mon cas que j'ai utilisé||
, en utilisantSUBSTRING_INDEX
cette liste, vous sélectionnerez le premierDémo de violon
la source
group_concat_max_len
limite le nombre de lignes que vous pouvez gérer.De toute évidence, il existe de nombreuses façons d'obtenir les mêmes résultats, votre question semble être quelle est la manière efficace d'obtenir les derniers résultats dans chaque groupe dans MySQL. Si vous travaillez avec d'énormes quantités de données et en supposant que vous utilisez InnoDB avec même les dernières versions de MySQL (telles que 5.7.21 et 8.0.4-rc), il pourrait ne pas y avoir de moyen efficace de le faire.
Nous devons parfois le faire avec des tables contenant encore plus de 60 millions de lignes.
Pour ces exemples, j'utiliserai des données avec seulement environ 1,5 million de lignes où les requêtes devraient trouver des résultats pour tous les groupes dans les données. Dans nos cas réels, nous aurions souvent besoin de renvoyer les données d'environ 2 000 groupes (ce qui, en théorie, ne nécessiterait pas d'examiner une grande partie des données).
J'utiliserai les tableaux suivants:
Le tableau des températures est peuplé d'environ 1,5 million d'enregistrements aléatoires et de 100 groupes différents. Le groupe sélectionné est peuplé de ces 100 groupes (dans nos cas, cela serait normalement inférieur à 20% pour tous les groupes).
Comme ces données sont aléatoires, cela signifie que plusieurs lignes peuvent avoir les mêmes horodatages enregistrés. Ce que nous voulons, c'est obtenir une liste de tous les groupes sélectionnés par ordre d'ID de groupe avec le dernier horodatage enregistré pour chaque groupe, et si le même groupe a plus d'une ligne correspondante comme celle-là, le dernier ID correspondant de ces lignes.
Si hypothétiquement MySQL avait une fonction last () qui renvoyait des valeurs de la dernière ligne d'une clause ORDER BY spéciale, alors nous pourrions simplement faire:
qui aurait seulement besoin d'examiner quelques 100 lignes dans ce cas car il n'utilise aucune des fonctions GROUP BY normales. Cela s'exécuterait en 0 secondes et serait donc très efficace. Notez que normalement dans MySQL, nous verrions une clause ORDER BY suivant la clause GROUP BY mais cette clause ORDER BY est utilisée pour déterminer l'ORDRE de la fonction last (), si elle était après le GROUP BY, elle ordonnerait les GROUPES. Si aucune clause GROUP BY n'est présente, les dernières valeurs seront les mêmes dans toutes les lignes renvoyées.
Cependant, MySQL ne l'a pas, alors examinons différentes idées de ce qu'il a et prouvons qu'aucune de celles-ci n'est efficace.
Exemple 1
Cela a examiné 3 009 254 rangées et a pris ~ 0,859 seconde sur 5.7.21 et légèrement plus long sur 8.0.4-rc
Exemple 2
Cela a examiné 1505331 rangées et a pris environ 1,25 seconde le 5.7.21 et légèrement plus long le 8.0.4-rc
Exemple 3
Cela a examiné 3 009 685 rangées et a pris environ 1,95 seconde le 5.7.21 et légèrement plus long le 8.0.4-rc
Exemple 4
Cela a examiné 6 137 810 rangées et a pris ~ 2,2 secondes le 5.7.21 et légèrement plus long le 8.0.4-rc
Exemple 5
Cela a examiné 6 017 808 rangées et a pris ~ 4,2 secondes sur 8.0.4-rc
Exemple 6
Cela a examiné 6 017 908 rangées et a pris ~ 17,5 secondes sur 8.0.4-rc
Exemple 7
Celui-ci prenait une éternité donc j'ai dû le tuer.
la source
SELECT DISTINCT(groupID)
est rapide et vous donnera toutes les données dont vous avez besoin pour construire une telle requête. Vous devriez être bien avec la taille de la requête tant qu'elle ne dépasse pasmax_allowed_packet
, ce qui par défaut est de 4 Mo dans MySQL 5.7.nous verrons comment vous pouvez utiliser MySQL pour obtenir le dernier enregistrement d'un regroupement d'enregistrements. Par exemple, si vous disposez de cet ensemble de résultats de publications.
id category_id post_title
1 1 Title 1
2 1 Title 2
3 1 Title 3
4 2 Title 4
5 2 Title 5
6 3 Title 6
Je veux pouvoir obtenir le dernier message dans chaque catégorie qui sont le titre 3, le titre 5 et le titre 6. Pour obtenir les messages par catégorie, vous utiliserez le clavier MySQL Group By.
select * from posts group by category_id
Mais les résultats que nous obtenons de cette requête sont.
id category_id post_title
1 1 Title 1
4 2 Title 4
6 3 Title 6
Le groupe par retournera toujours le premier enregistrement du groupe sur l'ensemble de résultats.
SELECT id, category_id, post_title FROM posts WHERE id IN ( SELECT MAX(id) FROM posts GROUP BY category_id );
Cela retournera les messages avec les identifiants les plus élevés dans chaque groupe.
id category_id post_title
3 1 Title 3
5 2 Title 5
6 3 Title 6
Référence Cliquez ici
la source
la source
Voici ma solution:
la source
SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME
.Essaye ça:
la source
Salut @Vijay Dev si vos messages de table contiennent l' ID qui est la clé primaire d'incrémentation automatique, alors pour récupérer la dernière base d'enregistrement sur la clé primaire, votre requête doit se lire comme ci-dessous:
la source
Vous pouvez également voir ici.
http://sqlfiddle.com/#!9/ef42b/9
PREMIÈRE SOLUTION
DEUXIÈME SOLUTION
la source
la source
**
Bonjour, cette requête pourrait aider:
**
la source
Existe-t-il un moyen d'utiliser cette méthode pour supprimer les doublons dans une table? Le jeu de résultats est essentiellement une collection d'enregistrements uniques, donc si nous pouvions supprimer tous les enregistrements qui ne se trouvent pas dans le jeu de résultats, nous n'aurions effectivement pas de doublons? J'ai essayé mais mySQL a donné une erreur 1093.
Existe-t-il un moyen de sauvegarder la sortie dans une variable temporaire puis de la supprimer de NOT IN (variable temporaire)? @Bill merci pour une solution très utile.
EDIT: Je pense avoir trouvé la solution:
la source
La requête ci-dessous fonctionnera correctement selon votre question.
la source
Si vous voulez la dernière ligne pour chacun
Name
, vous pouvez attribuer un numéro de ligne à chaque groupe de lignes par l'Name
ordre et parId
ordre décroissant.REQUETE
SQL Fiddle
la source
Que dis-tu de ça:
J'ai eu un problème similaire (sur postgresql difficile) et sur une table d'enregistrements 1M. Cette solution prend 1,7s contre 44s produites par celui avec LEFT JOIN. Dans mon cas, j'ai dû filtrer le corrigeant de votre champ de nom par rapport aux valeurs NULL, résultant en de meilleures performances de 0,2 seconde
la source
Si les performances sont vraiment votre préoccupation, vous pouvez introduire une nouvelle colonne sur la table appelée
IsLastInGroup
de type BIT.Réglez-le sur true sur les dernières colonnes et conservez-le à chaque insertion / mise à jour / suppression de ligne. Les écritures seront plus lentes, mais vous bénéficierez des lectures. Cela dépend de votre cas d'utilisation et je le recommande uniquement si vous êtes concentré sur la lecture.
Votre requête ressemblera donc à:
la source
la source
Vous pouvez grouper en comptant et obtenir également le dernier élément du groupe comme:
la source
J'espère que la requête Oracle ci-dessous peut vous aider:
la source
Une autre approche:
Trouvez la propriété avec le m2_price max avec chaque programme (n propriétés dans 1 programme):
la source