291 votes

Commande MySQL par devant le groupe de

Il ya beaucoup de questions similaires à être trouvé ici, mais je ne pense pas que la réponse à la question de manière adéquate.

Je vais continuer de les courants les plus populaires de la question et d'utiliser leur exemple, si c'est bien.

La tâche de cette instance est d'obtenir le dernier post pour chaque auteur dans la base de données.

L'exemple de requête produit inutilisable résultats n'est pas toujours le dernier post qui est retourné.

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

L'actuel accepté réponse 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 clair et simple mal et dans de nombreux cas, produit moins de résultats stables que l'original de la requête.

Par meilleure solution est d'utiliser une sous-requête de la forme

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 simple, puis: Est-il de toute façon à l'ordre des lignes avant le regroupement sans avoir recours à une sous-requête?

Edit: Cette question a été une continuation d'une autre question, et les détails de ma situation est légèrement différente. Vous pouvez (et devriez) supposons qu'il existe aussi un wp_posts.id est un identifiant unique pour ce poste particulier.

438voto

bluefeet Points 105508

À l'aide d'un ORDER BY dans une sous-requête n'est pas la meilleure solution à ce problème.

La meilleure solution pour obtenir le max(post_date) par l'auteur est d'utiliser une sous-requête pour renvoyer le max de date, puis joignez-le à votre table sur le post_author et le max de la date.

La solution doit être:

SELECT p1.* 
FROM wp_posts p1
INNER JOIN
(
    SELECT max(post_date) MaxPostDate, post_author
    FROM wp_posts
    WHERE post_status='publish'
       AND post_type='post'
    GROUP BY post_author
) p2
  ON p1.post_author = p2.post_author
  AND p1.post_date = p2.MaxPostDate
WHERE p1.post_status='publish'
  AND p1.post_type='post'
order by p1.post_date desc

Si vous avez les données d'exemple suivantes:

CREATE TABLE wp_posts
    (`id` int, `title` varchar(6), `post_date` datetime, `post_author` varchar(3))
;

INSERT INTO wp_posts
    (`id`, `title`, `post_date`, `post_author`)
VALUES
    (1, 'Title1', '2013-01-01 00:00:00', 'Jim'),
    (2, 'Title2', '2013-02-01 00:00:00', 'Jim')
;

La sous-requête va retourner au max la date et l'auteur de:

MaxDate  | Author
2/1/2013 | Jim

Alors, puisque vous vous joignez à ce retour à la table, sur les deux valeurs, vous pourrez retourner les détails complets de ce post.

Voir SQL jouer avec la Démo.

Afin d'élargir mes commentaires sur l'utilisation d'une sous-requête à la précision de ces données.

MySQL ne pas vous forcer à GROUP BY chaque colonne que vous incluez dans l' SELECT de la liste. Par conséquent, si vous n' GROUP BY d'une colonne, mais de retour de 10 colonnes au total, il n'y a aucune garantie que l'autre colonne de valeurs qui appartiennent à l' post_author qui est retourné. Si la colonne n'est pas en GROUP BY MySQL choisit quelle valeur doit être renvoyée.

À l'aide de la sous-requête avec la fonction d'agrégation permettra de garantir que le bon auteur et le poste est renvoyé à chaque fois.

Comme une note de côté, alors que MySQL vous permet d'utiliser un ORDER BY dans une sous-requête et vous permet d'appliquer un GROUP BY à pas chaque colonne, dans l' SELECT liste ce comportement n'est pas permis dans d'autres bases de données telles que SQL Server.

28voto

fthiella Points 21512

Votre solution utilise une extension de GROUPE PAR la clause qui permet au groupe de certains champs (dans ce cas, il suffit d' post_author):

GROUP BY wp_posts.post_author

et sélectionnez nonaggregated colonnes:

SELECT wp_posts.*

qui ne sont pas énuméré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 à la clause GROUP BY

Ceci est utile lorsque toutes les valeurs de non-agrégées colonnes sont égales pour chaque ligne.

Par exemple, supposons que vous avez une table GardensFlowers (name le jardin, flower qui pousse dans le jardin):

INSERT INTO GardensFlowers VALUES
('Central Park',       'Magnolia'),
('Hyde Park',          'Tulip'),
('Gardens By The Bay', 'Peony'),
('Gardens By The Bay', 'Cherry Blossom');

et vous voulez extraire toutes les fleurs qui pousse dans un jardin, où de multiples fleurs. Ensuite, vous devez utiliser une sous-requête, par exemple, vous pouvez utiliser ceci:

SELECT GardensFlowers.*
FROM   GardensFlowers
WHERE  name IN (SELECT   name
                FROM     GardensFlowers
                GROUP BY name
                HAVING   COUNT(DISTINCT flower)>1);

Si vous avez besoin d'extraire toutes les fleurs qui sont les seules fleurs à le garder au lieu de cela, vous pouvez simplement modifier la condition d'AVOIR d' HAVING COUNT(DISTINCT flower)=1, mais aussi MySql vous permet d'utiliser ceci:

SELECT   GardensFlowers.*
FROM     GardensFlowers
GROUP BY name
HAVING   COUNT(DISTINCT flower)=1;

pas de sous-requête, pas du SQL standard, mais en plus simple.

Une utilisation incorrecte de l'extension à la clause GROUP BY

Mais qu'advient-il si vous SÉLECTIONNEZ non-agrégées des colonnes qui sont sans égal pour chaque ligne? Qui est la valeur que MySql choisit pour cette colonne?

Il ressemble à MySql choisit toujours la PREMIÈRE valeur qu'elle rencontre.

Assurez-vous que la première valeur qu'elle rencontre est exactement la valeur que vous voulez, vous devez appliquer un GROUPE PAR un ordre de requête, d'où la nécessité de figu utilisant une sous-requête. Vous ne pouvez pas le faire sans avoir recours.

Compte tenu de l'hypothèse que MySql choisit toujours la première ligne qu'il rencontre, vous êtes correcly trier les lignes avant de le GROUPE. Mais malheureusement, si vous lisez attentivement la documentation, vous remarquerez que cette hypothèse n'est pas vrai.

Lors de la sélection de la non-agrégées colonnes qui ne sont pas toujours les mêmes, MySql est libre de choisir n'importe quelle valeur, de sorte que le montant de la valeur qu'il montre, en réalité, est de durée indéterminée.

Je vois que cette astuce pour obtenir la première valeur d'un non-agrégées colonne est beaucoup utilisé, et c'est souvent/presque toujours, je l'utilise aussi parfois (à mes risques et périls). Mais puisqu'il n'est pas documentée, vous ne pouvez pas compter sur ce comportement.

Ce lien (merci ypercube!) GROUPE PAR astuce a été optimisé à l'écart 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 autre moteur d'optimisation.

Donc, si cette astuce fonctionne, c'est juste une question de chance.

La accepté de répondre à l'autre question

La accepté de répondre à l'autre question ne semble pas correct du tout pour moi. Je pense que c'est faux:

HAVING wp_posts.post_date = MAX(wp_posts.post_date)

wp_posts.post_date est un non-agrégées de la colonne, et sa valeur sera officiellement indéterminée, mais il sera vraisemblablement le premier post_date rencontrées. Mais depuis que le GROUPE PAR la ruse est appliqué à une table non ordonnée, il n'est pas sûr de qui est le premier post_date rencontrées.

Il sera probablement retourne les messages qui sont les seuls postes d'un seul auteur, mais ce n'est pas toujours certaine.

Une solution possible

Je pense que cela pourrait être une solution possible:

SELECT wp_posts.*
FROM   wp_posts
WHERE  id IN (
  SELECT max(id)
  FROM wp_posts
  WHERE (post_author, post_date) = (
    SELECT   post_author, max(post_date)
    FROM     wp_posts
    WHERE    wp_posts.post_status='publish'
             AND wp_posts.post_type='post'
    GROUP BY post_author
  ) AND wp_posts.post_status='publish'
    AND wp_posts.post_type='post'
  GROUP BY post_author
)

Sur la requête interne, je suis de retour le maximum de post date pour chaque auteur. Je suis ensuite en prenant en considération le fait que le même auteur pourrait théoriquement avoir deux postes en même temps, donc je suis arriver au maximum de l'ID. Et puis je suis retourner toutes les lignes ceux maximale Id. Il peut être fait plus rapidement en utilisant des jointures au lieu de la clause.

(si vous êtes sûr que l'ID est toujours en augmentation, et si ID1>ID2 signifie également que post_date1>post_date2 la requête pourrait être beaucoup plus simple, mais je ne suis pas sûr si c'est le cas).

11voto

newtover Points 12301

Ce que vous allez lire est plutôt hacky, alors n'essayez pas ceci à la maison!

Dans SQL en général, la réponse à votre question est NON, mais à cause de l'atmosphère détendue de la mode de l' GROUP BY (mentionné par @bluefeet), la réponse est OUI dans MySQL.

Imaginons que vous ayez un index BTREE (post_status, post_type, post_author, post_date). Comment fonctionne l'indice de ressembler sous le capot?

(post_status='publier', post_type='post', post_author='utilisateur', post_date='2012-12-01') (post_status='publier', post_type='post', post_author='utilisateur', post_date='2012-12-31') (post_status='publier', post_type='post', post_author='utilisateur B', post_date='2012-10-01') (post_status='publier', post_type='post', post_author='utilisateur B', post_date='2012-12-01')

C'est les données sont triées par tous ces champs dans l'ordre croissant.

Lorsque vous faites 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 l' WHERE clause) et si il y a une correspondance de l'index, il faut des données pour chaque premier enregistrement dans l'ordre croissant. C'est la requête va chercher le suivant (le premier post de chaque utilisateur):

(post_status='publier', post_type='post', post_author='utilisateur', post_date='2012-12-01') (post_status='publier', post_type='post', post_author='utilisateur B', post_date='2012-10-01')

Mais GROUP BY dans MySQL permet de spécifier l'ordre explicite. Et lorsque vous demandez post_user dans l'ordre décroissant, il va marcher à travers notre index dans l'ordre inverse, en prenant toujours le premier enregistrement de chaque groupe qui est en fait la dernière.

C'est

...
WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author DESC

va nous donner

(post_status='publier', post_type='post', post_author='utilisateur B', post_date='2012-12-01') (post_status='publier', post_type='post', post_author='utilisateur', post_date='2012-12-31')

Maintenant, lorsque vous commandez les résultats du groupement par post_date, vous obtenez les données que vous vouliez.

SELECT wp_posts.*
FROM wp_posts
WHERE wp_posts.post_status='publish' AND wp_posts.post_type='post'
GROUP BY wp_posts.post_author DESC
ORDER BY wp_posts.post_date DESC;

NB:

Ce n'est pas ce que je recommanderais pour cette requête particulière. Dans ce cas, je voudrais utiliser une version légèrement modifiée de ce que @bluefeet suggère. Mais cette technique peut être très utile. Jetez un oeil à ma réponse ici: Récupérer le dernier enregistrement de chaque groupe

Les pièges: Les inconvénients de cette approche est qu'

  • le résultat de la requête dépend de l'indice, qui est contre l'esprit du SQL (index ne devrait accélérer les requêtes);
  • indice ne sait rien à propos de son influence sur la requête (vous ou quelqu'un d'autre à l'avenir pourrait trouver l'index est trop consommateur de ressources et de le changer en quelque sorte, de briser les résultats de la requête, et pas seulement de ses performances)
  • si vous ne comprenez pas comment la requête fonctionne, plus que probablement, vous allez oublier l'explication dans un mois et que la requête va vous embrouiller, vous et vos collègues.

L'avantage est de la performance dans les cas les plus sérieux. Dans ce cas, les performances de la requête doit être le même que dans @bluefeet de la requête, en raison de la quantité de données impliqué dans le tri (toutes les données sont chargées dans une table temporaire, puis triés; btw, sa requête nécessite l' (post_status, post_type, post_author, post_date) de l'indice).

Ce que je suggère:

Comme je l'ai dit, ces requêtes MySQL de temps à trier les déchets potentiellement d'énormes quantités de données dans une table temporaire. Dans le cas où vous avez besoin de pagination (c'est LIMITE) la plupart des données est même jeté. Ce que je voudrais faire est de minimiser la quantité de données triées: qui est une sorte de limite et d'un minimum de données dans la sous-requête et ensuite se joindre à l'ensemble du tableau.

SELECT * 
FROM wp_posts
INNER JOIN
(
  SELECT max(post_date) post_date, post_author
  FROM wp_posts
  WHERE post_status='publish' AND post_type='post'
  GROUP BY post_author
  ORDER BY post_date DESC
  -- LIMIT GOES HERE
) p2 USING (post_author, post_date)
WHERE post_status='publish' AND post_type='post';

La même requête à l'aide de la méthode décrite ci-dessus:

SELECT *
FROM (
  SELECT post_id
  FROM wp_posts
  WHERE post_status='publish' AND post_type='post'
  GROUP BY post_author DESC
  ORDER BY post_date DESC
  -- LIMIT GOES HERE
) as ids
JOIN wp_posts USING (post_id);

Toutes ces requêtes avec leurs plans d'exécution sur SQLFiddle.

9voto

Ray Z Points 465

Essayez celui-ci. Viens d’obtenir la liste des dates plus tardives de poste de chaque auteur. C’est elle

2voto

Dennisch Points 539

Lol Il est insensé de commander les enregistrements avant regroupement, puisque le regroupement va muter le jeu de résultats. Ailleurs, la sous-requête est préférable. Si cela va trop lentement vous devrez changer votre conception de la table, par exemple en stockant l’id de du dernier post pour chaque auteur dans une table séparée, ou introduire une colonne booléenne indiquant pour chaque auteur qui, de son poste, est le dernier.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X