web-dev-qa-db-de.com

MySQL "Group By" und "Order By"

Ich möchte in der Lage sein, eine Reihe von Zeilen aus einer E-Mail-Tabelle auszuwählen und diese nach Absender zu gruppieren. Meine Anfrage sieht so aus:

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

Die Abfrage funktioniert fast so, wie ich es möchte - sie wählt Datensätze aus, die per E-Mail gruppiert sind. Das Problem ist, dass der Betreff und der Zeitstempel nicht dem neuesten Datensatz für eine bestimmte E-Mail-Adresse entsprechen.

Beispielsweise könnte es zurückgeben:

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

Wenn die Datensätze in der Datenbank sind:

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

Wenn das Thema "Programmierfrage" das neueste ist, wie kann ich MySQL dazu bringen, diesen Datensatz beim Gruppieren der E-Mails auszuwählen?

89
John Kurlak

Eine einfache Lösung besteht darin, die Abfrage mit der ORDER-Anweisung first in eine Unterauswahl zu packen und GROUP BY later anzuwenden:

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

Dies ähnelt der Verwendung des Joins, sieht jedoch viel besser aus.

Die Verwendung von nicht aggregierten Spalten in einer SELECT-Anweisung mit einer GROUP BY-Klausel ist kein Standard. MySQL gibt im Allgemeinen die Werte der ersten gefundenen Zeile zurück und verwirft den Rest. Alle ORDER BY-Klauseln gelten nur für den zurückgegebenen Spaltenwert, nicht für die verworfenen.

WICHTIGES UPDATE Auswählen von nicht aggregierten Spalten, die in der Praxis verwendet werden, auf die man sich jedoch nicht verlassen sollte. Laut MySQL-Dokumentation "ist dies in erster Linie nützlich, wenn alle Werte in jeder nicht aggregierten Spalte, die nicht in GROUP BY genannt sind, für jede Gruppe gleich sind. Der Server ist frei Wählen Sie einen beliebigen Wert aus jeder Gruppe. Sofern diese nicht identisch sind, sind die ausgewählten Werte unbestimmt . "

Ab 5.6.21 habe ich Probleme mit GROUP BY in der temporären Tabelle festgestellt, die die Sortierung von ORDER BY rückgängig gemacht haben.

Ab 5.7.5 ONLY_FULL_GROUP_BY ist standardmäßig aktiviert, d. H. Es ist unmöglich, nicht aggregierte Spalten zu verwenden.

Siehe 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

Hier ist ein Ansatz:

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)

Grundsätzlich verbinden Sie die Tabelle auf sich selbst und suchen nach späteren Zeilen. In der where-Klausel geben Sie an, dass es keine späteren Zeilen geben darf. Dies gibt Ihnen nur die letzte Zeile.

Wenn es mehrere E-Mails mit demselben Zeitstempel geben kann, muss diese Abfrage verfeinert werden. Wenn die E-Mail-Tabelle eine inkrementelle ID-Spalte enthält, ändern Sie den JOIN wie folgt:

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

Erstellen Sie eine GROUP BY-Anweisung nach der ORDER BY-Anweisung, indem Sie Ihre Abfrage folgendermaßen mit der GROUP BY-Anweisung umschließen:

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

Wie bereits in einer Antwort erwähnt, ist die aktuelle Antwort falsch, da GROUP BY den Datensatz willkürlich aus dem Fenster auswählt.

Wenn Sie MySQL 5.6 oder MySQL 5.7 mit ONLY_FULL_GROUP_BY Verwenden, lautet die richtige (deterministische) Abfrage:

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

Damit die Abfrage effizient ausgeführt werden kann, ist eine ordnungsgemäße Indizierung erforderlich.

Beachten Sie, dass ich zur Vereinfachung die Funktion LOWER() entfernt habe, die in den meisten Fällen nicht verwendet wird.

24
Marcus

Gemäß SQL-Standard können Sie keine nicht aggregierten Spalten in der Auswahlliste verwenden. MySQL erlaubt eine solche Verwendung (es wird nur der ONLY_FULL_GROUP_BY-Modus verwendet), das Ergebnis ist jedoch nicht vorhersehbar.

ONLY_FULL_GROUP_BY

Sie sollten zuerst aus E-Mail, MIN (Lesen) und dann mit zweiter Abfrage (oder Unterabfrage) - Betreff auswählen.

21
noonex

Ich kämpfte mit beiden Ansätzen um komplexere Abfragen als die gezeigten, da der Ansatz für Unterabfragen, unabhängig von den von mir verwendeten Indizes, fürchterlich unzulänglich war und ich den äußeren Self-Join nicht über Hibernate erreichen konnte

Der beste (und einfachste) Weg, dies zu tun, besteht darin, eine Gruppierung nach etwas vorzunehmen, das so aufgebaut ist, dass es eine Verkettung der von Ihnen benötigten Felder enthält, und diese dann mit Ausdrücken in der SELECT-Klausel herauszuholen. Wenn Sie ein MAX () ausführen müssen, stellen Sie sicher, dass sich das Feld, über das Sie ein MAX () erstellen möchten, immer am höchstwertigen Ende der verketteten Entität befindet.

Der Schlüssel zum Verständnis ist, dass die Abfrage nur dann Sinn macht, wenn diese anderen Felder für eine Entität, die Max () erfüllt, unveränderlich sind. In Bezug auf die Sortierung können die anderen Teile der Verkettung ignoriert werden. Wie das geht, erfahren Sie ganz unten in diesem Link. http://dev.mysql.com/doc/refman/5.0/de/group-by-hidden-columns.html

Wenn Sie ein Einfüge-/Aktualisierungsereignis (wie einen Auslöser) erhalten, um die Verkettung der Felder vorab zu berechnen, können Sie es indizieren, und die Abfrage erfolgt so schnell, als ob die Gruppe nach genau dem Feld überlagert wäre, das Sie tatsächlich auf MAX setzen wollten ( ). Sie können es sogar verwenden, um maximal mehrere Felder abzurufen. Ich verwende es, um Abfragen für mehrdimensionale Bäume durchzuführen, die als verschachtelte Mengen ausgedrückt werden.

2
Mike N