Il y a beaucoup de questions similaires à trouver ici, mais je ne pense pas que quelqu'un y réponde correctement.
Je vais continuer de le courant le plus populaire question et d' utiliser leur exemple , si c'est bien.
La tâche dans ce cas est d'obtenir le dernier message pour chaque auteur dans la base de données.
L'exemple de requête produit des résultats inutilisables car ce n'est pas toujours le dernier message renvoyé.
SELECT wp_posts.* FROM wp_posts
WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author
ORDER BY wp_posts.post_date DESC
La réponse actuellement acceptée est
SELECT
wp_posts.*
FROM wp_posts
WHERE
wp_posts.post_status='publish'
AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author
HAVING wp_posts.post_date = MAX(wp_posts.post_date) <- ONLY THE LAST POST FOR EACH AUTHOR
ORDER BY wp_posts.post_date DESC
Malheureusement, cette réponse est tout simplement erronée et, dans de nombreux cas, produit des résultats moins stables que la requête d'origine.
Ma meilleure solution consiste à utiliser une sous-requête du formulaire
SELECT wp_posts.* FROM
(
SELECT *
FROM wp_posts
ORDER BY wp_posts.post_date DESC
) AS wp_posts
WHERE wp_posts.post_status='publish'
AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author
Ma question est alors simple: existe-t-il de toute façon de commander des lignes avant de les regrouper sans recourir à une sous-requête?
Edit : Cette question faisait suite à une autre question et les spécificités de ma situation sont légèrement différentes. Vous pouvez (et devez) supposer qu'il existe également un wp_posts.id qui est un identifiant unique pour cette publication particulière.
la source
post_author
etpost_date
ne suffisent pas pour obtenir une ligne unique, donc il doit y en avoir plus pour obtenir une ligne unique parpost_author
There are plenty of similar questions to be found on here but I don't think that any answer the question adequately.
C'est à cela que servent les primes.Réponses:
Utiliser un
ORDER BY
dans une sous-requête n'est pas la meilleure solution à ce problème.La meilleure solution pour obtenir l'
max(post_date)
auteur par est d'utiliser une sous-requête pour renvoyer la date maximale, puis la joindre à votre table à la fois à lapost_author
date maximale et à la date maximale.La solution devrait être:
Si vous disposez des exemples de données suivants:
La sous-requête va retourner la date maximale et l'auteur de:
Ensuite, puisque vous joignez cela à la table, sur les deux valeurs, vous retournerez tous les détails de ce message.
Voir SQL Fiddle avec démo .
Pour développer mes commentaires sur l'utilisation d'une sous-requête pour renvoyer avec précision ces données.
MySQL ne vous oblige pas à
GROUP BY
chaque colonne que vous incluez dans laSELECT
liste. Par conséquent, si vous n'avez qu'uneGROUP BY
seule colonne mais renvoyez 10 colonnes au total, rien ne garantit que les autres valeurs de colonne qui appartiennent à cellepost_author
qui est renvoyée. Si la colonne n'est pas dans unGROUP BY
MySQL, choisissez la valeur à renvoyer.L'utilisation de la sous-requête avec la fonction d'agrégation garantira que l'auteur et le message corrects sont retournés à chaque fois.
En guise de remarque, alors que MySQL vous permet d'utiliser un
ORDER BY
dans une sous-requête et vous permet d'appliquer unGROUP BY
à pas toutes les colonnes de laSELECT
liste, ce comportement n'est pas autorisé dans d'autres bases de données, y compris SQL Server.la source
wp_posts
sur les deux colonnes pour obtenir la ligne complète.GROUP BY
à une seule colonne, rien ne garantit que les valeurs des autres colonnes seront toujours correctes. Malheureusement, MySQL permet à ce type de SELECT / GROUPing de se produire, contrairement à d'autres produits. Deuxièmement, la syntaxe de l'utilisation d'unORDER BY
dans une sous-requête alors qu'il est autorisé dans MySQL n'est pas autorisée dans d'autres produits de base de données, y compris SQL Server. Vous devez utiliser une solution qui retournera le résultat correct à chaque exécution.INDEX(post_author, post_date)
est important.post_id
votre requête interne, alors techniquement, vous devez également la regrouper, ce qui fausserait probablement vos résultats.Votre solution utilise une extension de la clause GROUP BY qui permet de regrouper par certains champs (dans ce cas, juste
post_author
):et sélectionnez des colonnes non agrégées:
qui ne sont pas répertoriés dans la clause group by ou qui ne sont pas utilisés dans une fonction d'agrégation (MIN, MAX, COUNT, etc.).
Utilisation correcte de l'extension de la clause GROUP BY
Ceci est utile lorsque toutes les valeurs des colonnes non agrégées sont égales pour chaque ligne.
Par exemple, supposons que vous ayez une table
GardensFlowers
(name
du jardin,flower
qui pousse dans le jardin):et vous voulez extraire toutes les fleurs qui poussent dans un jardin, où poussent plusieurs fleurs. Ensuite, vous devez utiliser une sous-requête, par exemple, vous pouvez utiliser ceci:
Si vous devez extraire toutes les fleurs qui sont les seules fleurs dans le garder à la place, vous pouvez simplement changer la condition HAVING
HAVING COUNT(DISTINCT flower)=1
, mais MySql vous permet également d'utiliser ceci:pas de sous-requête, pas de SQL standard, mais plus simple.
Utilisation incorrecte de l'extension de la clause GROUP BY
Mais que se passe-t-il si vous sélectionnez des colonnes non agrégées qui ne sont pas égales pour chaque ligne? Quelle est la valeur que MySql choisit pour cette colonne?
Il semble que MySql choisisse toujours la PREMIÈRE valeur qu'il rencontre.
Pour vous assurer que la première valeur rencontrée est exactement la valeur souhaitée, vous devez appliquer un
GROUP BY
à une requête ordonnée, d'où la nécessité d'utiliser une sous-requête. Vous ne pouvez pas le faire autrement.Étant donné l'hypothèse que MySql choisit toujours la première ligne qu'il rencontre, vous triez correctement les lignes avant le GROUP BY. Mais malheureusement, si vous lisez attentivement la documentation, vous remarquerez que cette hypothèse n'est pas vraie.
Lors de la sélection de colonnes non agrégées qui ne sont pas toujours les mêmes, MySql est libre de choisir n'importe quelle valeur, de sorte que la valeur résultante qu'elle affiche réellement est indéterminée .
Je vois que cette astuce pour obtenir la première valeur d'une colonne non agrégée est beaucoup utilisée, et cela fonctionne généralement / presque toujours, je l'utilise aussi parfois (à mes risques et périls). Mais comme ce n'est pas documenté, vous ne pouvez pas vous fier à ce comportement.
Ce lien (merci ypercube!) L' astuce GROUP BY a été optimisé montre une situation dans laquelle la même requête renvoie des résultats différents entre MySql et MariaDB, probablement en raison d'un moteur d'optimisation différent.
Donc, si cette astuce fonctionne, c'est juste une question de chance.
La réponse acceptée à l'autre question me semble fausse:
wp_posts.post_date
est une colonne non agrégée, et sa valeur sera officiellement indéterminée, mais ce sera probablement la premièrepost_date
rencontrée. Mais puisque l'astuce GROUP BY est appliquée à une table non ordonnée, il n'est pas sûr de savoir quelle est la premièrepost_date
rencontrée.Il retournera probablement des articles qui sont les seuls articles d'un seul auteur, mais même cela n'est pas toujours certain.
Une solution possible
Je pense que cela pourrait être une solution possible:
Sur la requête interne, je renvoie la date de publication maximale pour chaque auteur. Je prends ensuite en considération le fait que le même auteur pourrait théoriquement avoir deux messages en même temps, donc je ne reçois que l'ID maximum. Et puis je retourne toutes les lignes qui ont ces ID maximum. Il pourrait être rendu plus rapide en utilisant des jointures au lieu de la clause IN.
(Si vous êtes sûr que cela
ID
ne fait qu'augmenter, et siID1 > ID2
cela signifie également celapost_date1 > post_date2
, alors la requête pourrait être rendue beaucoup plus simple, mais je ne suis pas sûr que ce soit le cas).la source
extension to GROUP By
une lecture intéressante, merci pour cela.Ce que vous allez lire est plutôt hacky, alors n'essayez pas ça à la maison!
En SQL en général, la réponse à votre question est NON , mais en raison du mode détendu de
GROUP BY
(mentionné par @bluefeet ), la réponse est OUI dans MySQL.Supposons que vous ayez un index BTREE sur (post_status, post_type, post_author, post_date). À quoi ressemble l'index sous le capot?
(post_status = 'publish', post_type = 'post', post_author = 'user A', post_date = '2012-12-01') (post_status = 'publish', post_type = 'post', post_author = 'user A', post_date = '2012-12-31') (post_status = 'publish', post_type = 'post', post_author = 'user B', post_date = '2012-10-01') (post_status = 'publish', post_type = ' post ', post_author =' user B ', post_date =' 2012-12-01 ')
Autrement dit, les données sont triées par tous ces champs dans l'ordre croissant.
Lorsque vous effectuez un
GROUP BY
par défaut, il trie les données par le champ de regroupement (post_author
, dans notre cas; post_status, post_type sont requis par laWHERE
clause) et s'il existe un index correspondant, il prend les données pour chaque premier enregistrement dans l'ordre croissant. C'est-à-dire que la requête va récupérer ce qui suit (le premier message pour chaque utilisateur):(post_status = 'publish', post_type = 'post', post_author = 'user A', post_date = '2012-12-01') (post_status = 'publish', post_type = 'post', post_author = 'user B', post_date = '2012-10-01')
Mais
GROUP BY
dans MySQL vous permet de spécifier explicitement l'ordre. Et lorsque vous demandezpost_user
dans l'ordre décroissant, il parcourra notre index dans l'ordre inverse, en prenant toujours le premier enregistrement pour chaque groupe qui est en fait le dernier.C'est
nous donnera
(post_status = 'publish', post_type = 'post', post_author = 'user B', post_date = '2012-12-01') (post_status = 'publish', post_type = 'post', post_author = 'user A', post_date = '2012-12-31')
Maintenant, lorsque vous commandez les résultats du regroupement par post_date, vous obtenez les données que vous vouliez.
NB :
Ce n'est pas ce que je recommanderais pour cette requête particulière. Dans ce cas, j'utiliserais une version légèrement modifiée de ce que suggère @bluefeet . Mais cette technique pourrait être très utile. Jetez un œil à ma réponse ici: Récupération du dernier enregistrement dans chaque groupe
Pièges : les inconvénients de l'approche sont que
L'avantage est la performance dans les cas difficiles. Dans ce cas, les performances de la requête doivent être les mêmes que dans la requête de @ bluefeet, en raison de la quantité de données impliquées dans le tri (toutes les données sont chargées dans une table temporaire puis triées; btw, sa requête nécessite également l'
(post_status, post_type, post_author, post_date)
index) .Ce que je suggérerais :
Comme je l'ai dit, ces requêtes font perdre du temps à MySQL pour trier des quantités potentiellement énormes de données dans une table temporaire. Si vous avez besoin de pagination (c'est-à-dire que LIMIT est impliqué), la plupart des données sont même supprimées. Ce que je ferais, c'est de minimiser la quantité de données triées: c'est-à-dire de trier et de limiter un minimum de données dans la sous-requête, puis de rejoindre la table entière.
La même requête en utilisant l'approche décrite ci-dessus:
Toutes ces requêtes avec leurs plans d'exécution sur SQLFiddle .
la source
Essaye celui-là. Obtenez simplement la liste des dernières dates de publication de chaque auteur . C'est tout
la source
post_date IN (select max(...) ...)
. C'est plus efficace que de faire un groupe par sous-sélection, voir dev.mysql.com/doc/refman/5.6/en/subquery-optimization.htmlIN ( SELECT ... )
est beaucoup moins efficace que l'équivalent JOIN.Non. Il est inutile de classer les enregistrements avant de les regrouper, car le regroupement va modifier le jeu de résultats. La voie de sous-requête est la voie préférée. Si cela va trop lentement, vous devrez changer la conception de votre table, par exemple en stockant l'id du dernier article pour chaque auteur dans une table séparée, ou introduire une colonne booléenne indiquant pour chaque auteur lequel de son article est le dernier une.
la source
Utilisez simplement la fonction max et la fonction group
la source
Pour récapituler, la solution standard utilise une sous-requête non corrélée et ressemble à ceci:
Si vous utilisez une ancienne version de MySQL ou un ensemble de données assez petit, vous pouvez utiliser la méthode suivante:
la source
** Les sous-requêtes peuvent avoir un impact négatif sur les performances lorsqu'elles sont utilisées avec de grands ensembles de données **
Requête d'origine
Requête modifiée
parce que j'utilise
max
dans leselect clause
==>max(p.post_date)
il est possible d'éviter les requêtes de sous-sélection et l'ordre par la colonne max après le groupe par.la source
Tout d'abord, n'utilisez pas * dans select, affecte leurs performances et empêche l'utilisation du groupe par et triez par. Essayez cette requête:
Lorsque vous ne spécifiez pas la table dans ORDER BY, juste l'alias, ils ordonneront le résultat de la sélection.
la source