web-dev-qa-db-fra.com

MySQL "Group By" et "Order By"

Je veux être en mesure de sélectionner un tas de lignes dans une table de courriels et de les regrouper par l'expéditeur. Ma requête ressemble à ceci:

SELECT 
    `timestamp`, `fromEmail`, `subject`
FROM `incomingEmails` 
GROUP BY LOWER(`fromEmail`) 
ORDER BY `timestamp` DESC

La requête fonctionne presque comme je le souhaite - elle sélectionne les enregistrements groupés par courrier électronique. Le problème est que le sujet et l'horodatage ne correspondent pas à l'enregistrement le plus récent pour une adresse de messagerie particulière.

Par exemple, il pourrait renvoyer:

fromEmail: [email protected], subject: hello
fromEmail: [email protected], subject: welcome

Lorsque les enregistrements de la base de données sont:

fromEmail: [email protected], subject: hello
fromEmail: [email protected], subject: programming question
fromEmail: [email protected], subject: welcome

Si le sujet de la "question de programmation" est le plus récent, comment puis-je obtenir que MySQL sélectionne cet enregistrement lors du regroupement des e-mails?

89
John Kurlak

Une solution simple consiste à envelopper la requête dans une sous-sélection avec l'instruction ORDER first et à appliquer GROUP BY plus tard:

SELECT * FROM ( 
    SELECT `timestamp`, `fromEmail`, `subject`
    FROM `incomingEmails` 
    ORDER BY `timestamp` DESC
) AS tmp_table GROUP BY LOWER(`fromEmail`)

Ceci est similaire à l'utilisation de la jointure mais semble beaucoup plus agréable.

L'utilisation de colonnes non agrégées dans un SELECT avec une clause GROUP BY est non standard. MySQL renverra généralement les valeurs de la première ligne trouvée et éliminera le reste. Toutes les clauses ORDER BY s'appliqueront uniquement à la valeur de colonne renvoyée, pas à celles supprimées.

MISE À JOUR IMPORTANTE La sélection des colonnes non agrégées utilisées pour fonctionner dans la pratique mais ne doit pas être invoquée. Pour documentation MySQL "ceci est utile principalement lorsque toutes les valeurs de chaque colonne non agrégée non nommées dans GROUP BY sont identiques pour chaque groupe. Le serveur est libre de choisissez n'importe quelle valeur dans chaque groupe, donc , à moins qu'elles ne soient identiques, les valeurs choisies sont indéterminées . "

À compter du 5.6.21, j'ai remarqué des problèmes avec GROUP BY sur la table temporaire lors de l'inversion du tri ORDER BY.

À compter de 5.7.5 , ONLY_FULL_GROUP_BY est activé par défaut, c’est-à-dire qu’il est impossible d’utiliser des colonnes non agrégées.

Voir http://www.cafewebmaster.com/mysql-order-sort-grouphttps://dev.mysql.com/doc/refman/5.6/en/group-by -handling.htmlhttps://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html

132
b7kich

Voici une approche:

SELECT cur.textID, cur.fromEmail, cur.subject, 
     cur.timestamp, cur.read
FROM incomingEmails cur
LEFT JOIN incomingEmails next
    on cur.fromEmail = next.fromEmail
    and cur.timestamp < next.timestamp
WHERE next.timestamp is null
and cur.toUserID = '$userID' 
ORDER BY LOWER(cur.fromEmail)

Fondamentalement, vous joignez la table sur lui-même, en recherchant les lignes suivantes. Dans la clause where, vous indiquez qu'il ne peut y avoir de lignes ultérieures. Cela ne vous donne que la dernière ligne.

S'il peut y avoir plusieurs courriels avec le même horodatage, cette requête devra être affinée. S'il existe une colonne d'identifiant incrémentiel dans la table de courrier électronique, modifiez le paramètre JOIN de la manière suivante:

LEFT JOIN incomingEmails next
    on cur.fromEmail = next.fromEmail
    and cur.id < next.id
42
Andomar

Faites un GROUP BY après le ORDER BY en encapsulant votre requête avec le GROUP BY comme ceci:

SELECT t.* FROM (SELECT * FROM table ORDER BY time DESC) t GROUP BY t.from
29
11101101b

Comme indiqué dans une réponse, la réponse actuelle est fausse, car GROUP BY sélectionne arbitrairement l'enregistrement dans la fenêtre.

Si on utilise MySQL 5.6 ou MySQL 5.7 avec ONLY_FULL_GROUP_BY, La requête correcte (déterministe) est la suivante:

SELECT incomingEmails.*
  FROM (
    SELECT fromEmail, MAX(timestamp) `timestamp`
    FROM incomingEmails
    GROUP BY fromEmail
  ) filtered_incomingEmails
  JOIN incomingEmails USING (fromEmail, timestamp)
GROUP BY fromEmail, timestamp

Pour que la requête s'exécute efficacement, une indexation correcte est requise.

Notez que par souci de simplification, j'ai supprimé la LOWER(), qui, dans la plupart des cas, ne sera pas utilisée.

24
Marcus

Selon le standard SQL, vous ne pouvez pas utiliser de colonnes non agrégées dans la liste de sélection. MySQL autorise une telle utilisation (mode sans utilisation ONLY_FULL_GROUP_BY utilisé), mais le résultat n’est pas prévisible.

ONLY_FULL_GROUP_BY

Vous devez d’abord choisir parmi Email, MIN (lire), puis avec une seconde requête (ou sous-requête) - Objet.

21
noonex

J'ai eu du mal à utiliser ces deux approches pour des requêtes plus complexes que celles présentées, car l'approche des sous-requêtes était horriblement inefficace, peu importe les index que j'ai mis, et parce que je ne pouvais pas obtenir l'auto-jointure externe via Hibernate.

La meilleure façon (et la plus simple) de procéder consiste à regrouper les éléments dont la construction contient les éléments nécessaires à la concaténation, puis à les extraire à l'aide d'expressions de la clause SELECT. Si vous avez besoin de faire un MAX (), assurez-vous que le champ sur lequel vous souhaitez appliquer le MAX () est toujours situé à l'extrémité la plus significative de l'entité concaténée.

La clé pour comprendre cela est que la requête ne peut avoir un sens que si ces autres champs sont invariants pour toute entité satisfaisant à Max (). Ainsi, en termes de tri, les autres éléments de la concaténation peuvent être ignorés. Il explique comment faire cela tout en bas de ce lien. http://dev.mysql.com/doc/refman/5.0/en/group-by-hidden-columns.html

Si vous pouvez obtenir un événement insert/update (comme un déclencheur) pour pré-calculer la concaténation des champs, vous pouvez l'indexer et la requête sera aussi rapide que si le groupe était uniquement sur le champ que vous vouliez réellement créer dans MAX ( ). Vous pouvez même l'utiliser pour obtenir le maximum de plusieurs champs. Je l'utilise pour faire des requêtes sur des arbres multi-dimensionnels exprimés sous forme d'ensembles imbriqués.

2
Mike N