2009-06-30 7 views
70

Ich möchte in der Lage sein, eine Reihe von Zeilen aus einer Tabelle von E-Mails auswählen und gruppieren sie nach Absender. Meine Abfrage sieht so aus:MySQL "Group By" und "Order By"

Die Abfrage funktioniert fast so, wie ich es will - es wählt Datensätze nach E-Mail gruppiert. Das Problem besteht darin, dass der Betreff und der Zeitstempel nicht dem neuesten Datensatz für eine bestimmte E-Mail-Adresse entsprechen.

Zum Beispiel könnte es zurück:

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 die „Programmierung Frage“ Thema der jüngste ist, wie kann ich MySQL um diesen Datensatz beim Gruppieren der E-Mails auszuwählen?

Antwort

110

Eine einfache Lösung ist die Abfrage in eine subselect mit der ORDER Anweisung ersten und Anwenden der GROUP BY später einzuwickeln:

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

Dies ist ähnlich wie bei der Verbindung sieht aber viel schöner.

Die Verwendung von nicht aggregierten Spalten in einem SELECT mit einer GROUP BY-Klausel ist nicht standardisiert. MySQL gibt normalerweise 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.

WICHTIG UPDATE Auswahl Nicht-Aggregat-Spalten verwendet in der Praxis zu arbeiten, aber sollte sich nicht darauf verlassen. Pro MySQL documentation "Dies ist in erster Linie nützlich, wenn alle Werte in jeder nichtaggregierten Spalte, die nicht in der GROUP BY benannt sind, für jede Gruppe gleich sind. Der Server ist frei, um einen Wert aus jeder Gruppe zu wählen, also , wenn sie gleich sind, die gewählten Werte sind unbestimmt. "

Ab 5.6.21 habe ich Probleme mit der GROUP BY in der temporären Tabelle festgestellt, die die ORDER BY-Sortierung zurücksetzt.

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

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

+0

Tolle Idee, ich hätte nie gedacht, es so zu machen. – philwilks

+4

Ich habe vor ein paar Jahren die gleiche Lösung gefunden, und es ist eine großartige Lösung. ein großes Lob an b7kich. Zwei Probleme hier aber ...GROUP BY unterscheidet nicht zwischen Groß- und Kleinschreibung, LOWER() ist unnötig, und zweitens, $ userID scheint eine Variable direkt aus PHP zu sein. Ihr Code kann sql injection angreifbar sein, wenn $ userID vom Benutzer angegeben wird und nicht zu einer Ganzzahl wird. – velcrow

+0

Schöne Idee. Vielen Dank –

40

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 kommen Sie in der Tabelle auf sich selbst, für die spätere Reihen zu suchen. In der where-Klausel geben Sie an, dass es keine späteren Zeilen geben darf. Dies gibt Ihnen nur die letzte Zeile.

Wenn mehrere E-Mails mit demselben Zeitstempel vorliegen, müsste diese Abfrage verfeinert werden. Wenn es eine inkrementelle ID-Spalte in der E-Mail-Tabelle ist, die JOIN ändern wie:

LEFT JOIN incomingEmails next 
    on cur.fromEmail = next.fromEmail 
    and cur.id < next.id 
+0

Gesagt, dass 'textID' mehrdeutig war =/ –

+1

Dann entfernen Sie die Ambiguität und setzen Sie sie mit dem Tabellennamen, wie cur.textID. Auch in der Antwort geändert. – Andomar

+0

Dies ist die einzige Lösung, die mit Doctrine DQL möglich ist. – VisioN

21

Nach SQL-Standard nicht Nicht-Aggregat-Spalten in der Auswahlliste verwenden kann. MySQL erlaubt solche Verwendung (Uless ONLY_FULL_GROUP_BY Modus verwendet), aber das Ergebnis ist nicht vorhersehbar.

ONLY_FULL_GROUP_BY

Sie sollten zuerst wählen FROMEMAIL, MIN (Lesen) und dann mit der zweiten Abfrage (oder Unterabfrage) - Subject.

+0

MIN (gelesen) würde den minimalen Wert von "read" zurückgeben. Wahrscheinlich sucht er stattdessen nach der "Lese" -Flagge der letzten E-Mail. – Andomar

2

ich mit diesen beiden Ansätzen für komplexere Abfragen als die gezeigten gerungen, weil die Unterabfrage Ansatz schrecklich ineficient egal war, was Indizes Ich habe auf, und da konnte ich nicht Holen Sie sich den äußeren Self-Join durch Hibernate

Die beste (und einfachste) Möglichkeit, dies zu tun ist, durch etwas zu gruppieren, die eine Verkettung der Felder enthalten, die Sie benötigen, und dann herausziehen sie mit SELECT-Ausdrücke Klausel. Wenn Sie eine MAX() -Methode ausführen müssen, stellen Sie sicher, dass das Feld, für das Sie MAX() überschreiben möchten, immer am signifikantesten Ende der verketteten Entität liegt. Der Schlüssel zum Verständnis ist, dass die Abfrage nur Sinn machen kann, wenn diese anderen Felder für jede Entität, die die Max() erfüllt, invariant sind, so dass die anderen Teile der Verkettung hinsichtlich der Sortierung ignoriert werden können. Es wird erklärt, wie dies am Ende dieser Verknüpfung zu tun ist. http://dev.mysql.com/doc/refman/5.0/en/group-by-hidden-columns.html

Wenn Sie am insert/update-Ereignisse (wie ein Auslöser), um die Verkettung der Felder im Voraus berechnen können Sie indizieren und die Abfrage so schnell sein werden, als ob die Gruppe um mehr als nur das Feld war man wollte eigentlich MAX(). Sie können es sogar verwenden, um das Maximum von mehreren Feldern zu erhalten. Ich benutze es, um Abfragen gegen mehrdimensionale Bäume zu machen, die als verschachtelte Mengen ausgedrückt werden.

24

Führen Sie eine GROUP BY nach der ORDER BY durch Ihre Frage mit der GROUP BY wie diese Verpackung:

SELECT t.* FROM (SELECT * FROM table ORDER BY time DESC) t GROUP BY t.from 
+0

Danke Das funktionierte perfekt für mich bei einer ähnlichen Abfrage, die ich gerade machte. – Mark

+0

Also die GROUP BY "wählt automatisch die späteste" Zeit "oder die neueste" Zeit ", oder zufällig? – xrDDDD

+0

Es wählt die neueste Uhrzeit aus, weil wir nach 'time DESC' bestellen und dann die Gruppe nach der ersten (neusten). – 11101101b

12

Wie bereits in einer Antwort darauf, die aktuelle Antwort ist falsch, weil die GROUP BY willkürlich die wählt Aufzeichnung aus dem Fenster.

Wenn man MySQL 5.6 oder MySQL 5.7 mit ONLY_FULL_GROUP_BY verwenden, die richtige (deterministisch) 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 effizienter ausgeführt werden, Indexierung erforderlich ist.

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