Es gibt eine Tabelle messages
, die die folgenden Daten enthält:
Id Name Other_Columns
-------------------------
1 A A_data_1
2 A A_data_2
3 A A_data_3
4 B B_data_1
5 B B_data_2
6 C C_data_1
Wenn ich eine Abfrage ausführe select * from messages group by name
, erhalte ich folgendes Ergebnis:
1 A A_data_1
4 B B_data_1
6 C C_data_1
Welche Abfrage gibt das folgende Ergebnis zurück?
3 A A_data_3
5 B B_data_2
6 C C_data_1
Das heißt, der letzte Datensatz in jeder Gruppe sollte zurückgegeben werden.
Derzeit ist dies die Abfrage, die ich verwende:
SELECT
*
FROM (SELECT
*
FROM messages
ORDER BY id DESC) AS x
GROUP BY name
Das sieht aber sehr ineffizient aus. Gibt es noch andere Möglichkeiten, um das gleiche Ergebnis zu erzielen?
sql
mysql
group-by
greatest-n-per-group
Vijay Dev
quelle
quelle
Antworten:
MySQL 8.0 unterstützt jetzt Fensterfunktionen, wie fast alle gängigen SQL-Implementierungen. Mit dieser Standardsyntax können wir Abfragen mit den größten n pro Gruppe schreiben:
Unten ist die ursprüngliche Antwort, die ich 2009 auf diese Frage geschrieben habe:
Ich schreibe die Lösung so:
In Bezug auf die Leistung kann die eine oder andere Lösung je nach Art Ihrer Daten besser sein. Sie sollten also beide Abfragen testen und die verwenden, die angesichts Ihrer Datenbank eine bessere Leistung bietet.
Zum Beispiel habe ich eine Kopie des StackOverflow August-Datendumps . Ich werde das für das Benchmarking verwenden. Die
Posts
Tabelle enthält 1.114.357 Zeilen . Dies läuft unter MySQL 5.0.75 auf meinem Macbook Pro 2.40GHz.Ich werde eine Abfrage schreiben, um den neuesten Beitrag für eine bestimmte Benutzer-ID (meine) zu finden.
Verwenden Sie zuerst die von @Eric gezeigte Technik mit der
GROUP BY
in einer Unterabfrage:Sogar die
EXPLAIN
Analyse dauert über 16 Sekunden:Erstellen Sie nun dasselbe Abfrageergebnis mit meiner Technik mit
LEFT JOIN
:Die
EXPLAIN
Analyse zeigt, dass beide Tabellen ihre Indizes verwenden können:Hier ist die DDL für meine
Posts
Tabelle:quelle
<=
hilft nicht, wenn Sie eine nicht eindeutige Spalte haben. Sie müssen eine eindeutige Spalte als Tiebreaker verwenden.UPD: 2017-03-31, die Version 5.7.5 von MySQL hat den Schalter ONLY_FULL_GROUP_BY standardmäßig aktiviert (daher wurden nicht deterministische GROUP BY-Abfragen deaktiviert). Darüber hinaus wurde die GROUP BY-Implementierung aktualisiert, und die Lösung funktioniert möglicherweise nicht mehr wie erwartet, selbst wenn der Switch deaktiviert ist. Man muss überprüfen.
Die obige Lösung von Bill Karwin funktioniert einwandfrei, wenn die Anzahl der Elemente innerhalb von Gruppen eher gering ist. Die Leistung der Abfrage wird jedoch schlecht, wenn die Gruppen ziemlich groß sind, da für die Lösung
n*n/2 + n/2
nurIS NULL
Vergleiche erforderlich sind .Ich habe meine Tests an einer InnoDB-Tabelle mit
18684446
Zeilen mit1182
Gruppen durchgeführt. Die Tabelle enthält Testergebnisse für Funktionstests und hat den(test_id, request_id)
als Primärschlüssel. Somittest_id
ist eine Gruppe und ich haberequest_id
für jede nach der letzten gesuchttest_id
.Bills Lösung läuft bereits seit mehreren Stunden auf meinem Dell E4310 und ich weiß nicht, wann es fertig sein wird, obwohl es mit einem Coverage-Index arbeitet (daher)
using index
in EXPLAIN).Ich habe einige andere Lösungen, die auf denselben Ideen basieren:
(group_id, item_value)
Paar der letzte Wert in jedemgroup_id
, dh der erste für jedengroup_id
wenn wir den Index in absteigender Reihenfolge durchlaufen.3 Möglichkeiten, wie MySQL Indizes verwendet, sind ein großartiger Artikel, um einige Details zu verstehen.
Lösung 1
Dieser ist unglaublich schnell, es dauert ungefähr 0,8 Sekunden in meinen 18M + Reihen:
Wenn Sie die Reihenfolge in ASC ändern möchten, fügen Sie sie in eine Unterabfrage ein, geben Sie nur die IDs zurück und verwenden Sie diese als Unterabfrage, um sie mit den übrigen Spalten zu verbinden:
Dieser dauert ungefähr 1,2 Sekunden für meine Daten.
Lösung 2
Hier ist eine andere Lösung, die für meinen Tisch ungefähr 19 Sekunden dauert:
Es werden auch Tests in absteigender Reihenfolge zurückgegeben. Es ist viel langsamer, da es einen vollständigen Index-Scan durchführt, aber es soll Ihnen eine Vorstellung davon geben, wie N max Zeilen für jede Gruppe ausgegeben werden.
Der Nachteil der Abfrage besteht darin, dass ihr Ergebnis nicht vom Abfragecache zwischengespeichert werden kann.
quelle
SELECT test_id, request_id FROM testresults GROUP BY test_id;
würde die minimale request_id für jede test_id zurückgeben.Verwenden Sie Ihre Unterabfrage , um die richtige Gruppierung zurückzugeben, da Sie auf halbem Weg sind.
Versuche dies:
Wenn nicht
id
, möchten Sie das Maximum von:Auf diese Weise vermeiden Sie korrelierte Unterabfragen und / oder Ordnungen in Ihren Unterabfragen, die in der Regel sehr langsam / ineffizient sind.
quelle
other_col
: Wenn diese Spalte nicht eindeutig ist, erhalten Sie möglicherweise mehrere Datensätze mit derselben zurückname
, wenn sie übereinstimmenmax(other_col)
. Ich habe diesen Beitrag gefunden , der eine Lösung für meine Bedürfnisse beschreibt, bei der ich genau einen Datensatz pro benötigename
.INDEX(name, id)
undINDEX(name, other_col)
Ich bin zu einer anderen Lösung gekommen, nämlich die IDs für den letzten Beitrag in jeder Gruppe abzurufen und dann aus der Nachrichtentabelle das Ergebnis der ersten Abfrage als Argument für ein
WHERE x IN
Konstrukt auszuwählen :Ich weiß nicht, wie dies im Vergleich zu einigen anderen Lösungen funktioniert, aber es hat für meinen Tisch mit mehr als 3 Millionen Zeilen spektakulär funktioniert. (4 Sekunden Ausführung mit 1200+ Ergebnissen)
Dies sollte sowohl unter MySQL als auch unter SQL Server funktionieren.
quelle
Lösung durch Unterabfrage Geige Link
Lösung Durch Join-Bedingung Geigen-Link
Grund für diesen Beitrag ist, nur Geigenlink zu geben. Dasselbe SQL wird bereits in anderen Antworten bereitgestellt.
quelle
Ein Ansatz mit beträchtlicher Geschwindigkeit ist wie folgt.
Ergebnis
quelle
id
dass die Bestellung so erfolgt, wie Sie sie benötigen. Im allgemeinen Fall wird eine andere Spalte benötigt.Hier sind zwei Vorschläge. Erstens ist es sehr einfach, wenn MySQL ROW_NUMBER () unterstützt:
Ich gehe davon aus, dass Sie mit "last" das letzte in der ID-Reihenfolge meinen. Wenn nicht, ändern Sie die ORDER BY-Klausel des Fensters ROW_NUMBER () entsprechend. Wenn ROW_NUMBER () nicht verfügbar ist, ist dies eine andere Lösung:
Zweitens ist dies oft ein guter Weg, um fortzufahren:
Mit anderen Worten, wählen Sie Nachrichten aus, bei denen keine spätere ID-Nachricht mit demselben Namen vorhanden ist.
quelle
ROW_NUMBER()
und CTEs.Ich habe noch nicht mit einer großen Datenbank getestet, aber ich denke, dies könnte schneller sein als das Verbinden von Tabellen:
quelle
Hier ist eine andere Möglichkeit, den letzten zugehörigen Datensatz
GROUP_CONCAT
mit der Reihenfolge vonSUBSTRING_INDEX
abzurufen und einen der Datensätze aus der Liste auszuwählenDie obige Abfrage gruppiert alle, die
Other_Columns
sich in derselbenName
Gruppe befinden, und verwendetORDER BY id DESC
alleOther_Columns
in einer bestimmten Gruppe in absteigender Reihenfolge mit dem bereitgestellten Trennzeichen, das ich in meinem Fall verwendet habe||
. Wenn SieSUBSTRING_INDEX
über diese Liste verwenden, wird das erste ausgewähltGeigen-Demo
quelle
group_concat_max_len
die Anzahl der Zeilen begrenzt wird, die Sie verarbeiten können.Natürlich gibt es viele verschiedene Möglichkeiten, um die gleichen Ergebnisse zu erzielen. Ihre Frage scheint zu sein, wie Sie die letzten Ergebnisse in jeder Gruppe in MySQL effizient erzielen können. Wenn Sie mit großen Datenmengen arbeiten und davon ausgehen, dass Sie InnoDB auch mit den neuesten Versionen von MySQL (wie 5.7.21 und 8.0.4-rc) verwenden, gibt es möglicherweise keine effiziente Möglichkeit, dies zu tun.
Manchmal müssen wir dies mit Tabellen mit sogar mehr als 60 Millionen Zeilen tun.
Für diese Beispiele verwende ich Daten mit nur etwa 1,5 Millionen Zeilen, in denen die Abfragen Ergebnisse für alle Gruppen in den Daten finden müssten. In unseren tatsächlichen Fällen müssten wir häufig Daten von etwa 2.000 Gruppen zurückgeben (was hypothetisch nicht erfordern würde, sehr viele Daten zu untersuchen).
Ich werde die folgenden Tabellen verwenden:
Die Temperaturtabelle enthält etwa 1,5 Millionen zufällige Datensätze und 100 verschiedene Gruppen. Die selected_group wird mit diesen 100 Gruppen gefüllt (in unseren Fällen wären dies normalerweise weniger als 20% für alle Gruppen).
Da diese Daten zufällig sind, bedeutet dies, dass mehrere Zeilen dieselben aufgezeichneten Zeitstempel haben können. Wir möchten eine Liste aller ausgewählten Gruppen in der Reihenfolge der Gruppen-ID mit dem zuletzt aufgezeichneten Zeitstempel für jede Gruppe erhalten. Wenn dieselbe Gruppe mehr als eine übereinstimmende Zeile hat, dann die letzte übereinstimmende ID dieser Zeilen.
Wenn MySQL hypothetisch eine last () -Funktion hätte, die Werte aus der letzten Zeile in einer speziellen ORDER BY-Klausel zurückgibt, könnten wir einfach Folgendes tun:
In diesem Fall müssten nur einige 100 Zeilen untersucht werden, da keine der normalen GROUP BY-Funktionen verwendet wird. Dies würde in 0 Sekunden ausgeführt und wäre daher hocheffizient. Beachten Sie, dass in MySQL normalerweise eine ORDER BY-Klausel nach der GROUP BY-Klausel angezeigt wird. Diese ORDER BY-Klausel wird jedoch verwendet, um die ORDER für die last () -Funktion zu bestimmen. Wenn sie nach GROUP BY liegt, werden die GROUPS bestellt. Wenn keine GROUP BY-Klausel vorhanden ist, sind die letzten Werte in allen zurückgegebenen Zeilen gleich.
MySQL hat dies jedoch nicht. Schauen wir uns also verschiedene Ideen an und beweisen, dass keines davon effizient ist.
Beispiel 1
Dies untersuchte 3.009.254 Reihen und dauerte am 5.7.21 ~ 0,859 Sekunden und am 8.0.4-rc etwas länger
Beispiel 2
Dies untersuchte 1.505.331 Reihen und dauerte am 5.7.21 ~ 1,25 Sekunden und am 8.0.4-rc etwas länger
Beispiel 3
Dies untersuchte 3.009.685 Reihen und dauerte am 5.7.21 ~ 1,95 Sekunden und am 8.0.4-rc etwas länger
Beispiel 4
Dies untersuchte 6.137.810 Reihen und dauerte am 5.7.21 ~ 2,2 Sekunden und am 8.0.4-rc etwas länger
Beispiel 5
Dies untersuchte 6.017.808 Reihen und dauerte bei 8,0,4-rc ~ 4,2 Sekunden
Beispiel 6
Dies untersuchte 6.017.908 Reihen und dauerte bei 8,0,4-rc ~ 17,5 Sekunden
Beispiel 7
Dieser dauerte ewig, also musste ich ihn töten.
quelle
SELECT DISTINCT(groupID)
ist schnell und gibt Ihnen alle Daten, die Sie zum Erstellen einer solchen Abfrage benötigen. Die Abfragegröße sollte in Ordnung sein, solange sie nicht überschrittenmax_allowed_packet
wird. In MySQL 5.7 beträgt der Standardwert 4 MB.Wir werden untersuchen, wie Sie MySQL verwenden können, um den letzten Datensatz in einer Gruppe von Datensätzen abzurufen. Zum Beispiel, wenn Sie diese Ergebnismenge von Beiträgen haben.
id category_id post_title
1 1 Title 1
2 1 Title 2
3 1 Title 3
4 2 Title 4
5 2 Title 5
6 3 Title 6
Ich möchte in der Lage sein, den letzten Beitrag in jeder Kategorie zu erhalten, nämlich Titel 3, Titel 5 und Titel 6. Um die Beiträge nach Kategorie zu erhalten, verwenden Sie die MySQL Group By-Tastatur.
select * from posts group by category_id
Die Ergebnisse dieser Abfrage sind jedoch.
id category_id post_title
1 1 Title 1
4 2 Title 4
6 3 Title 6
Die Gruppe von gibt immer den ersten Datensatz in der Gruppe in der Ergebnismenge zurück.
SELECT id, category_id, post_title FROM posts WHERE id IN ( SELECT MAX(id) FROM posts GROUP BY category_id );
Dadurch werden die Beiträge mit den höchsten IDs in jeder Gruppe zurückgegeben.
id category_id post_title
3 1 Title 3
5 2 Title 5
6 3 Title 6
Referenz Klicken Sie hier
quelle
quelle
Hier ist meine Lösung:
quelle
SELECT NAME, MAX(MESSAGES) MESSAGES FROM MESSAGE GROUP BY NAME
.Versuche dies:
quelle
Hallo @Vijay Dev wenn Ihre Tabelle Nachrichten enthalten Id , die dann Primärschlüssel Autoinkrement ist die letzte Aufzeichnung Basis auf dem Primärschlüssel zu holen Ihre Abfrage wie unten sollte lauten:
quelle
Sie können auch von hier aus sehen.
http://sqlfiddle.com/#!9/ef42b/9
ERSTE LÖSUNG
ZWEITE LÖSUNG
quelle
quelle
** **.
Hallo, diese Abfrage könnte helfen:
** **.
quelle
Gibt es eine Möglichkeit, mit dieser Methode Duplikate in einer Tabelle zu löschen? Die Ergebnismenge ist im Grunde eine Sammlung eindeutiger Datensätze. Wenn wir also alle Datensätze löschen könnten, die nicht in der Ergebnismenge enthalten sind, hätten wir effektiv keine Duplikate. Ich habe es versucht, aber mySQL hat einen 1093-Fehler ausgegeben.
Gibt es eine Möglichkeit, die Ausgabe in einer temporären Variablen zu speichern und dann aus NOT IN (temporäre Variable) zu löschen? @ Bill danke für eine sehr nützliche Lösung.
EDIT: Ich glaube, ich habe die Lösung gefunden:
quelle
Die folgende Abfrage funktioniert gemäß Ihrer Frage einwandfrei.
quelle
Wenn Sie die letzte Zeile für jede Zeile möchten
Name
, können Sie jeder Zeilengruppe eine Zeilennummer nachName
undId
in absteigender Reihenfolge geben.ABFRAGE
SQL Fiddle
quelle
Wie wäre es damit:
Ich hatte ein ähnliches Problem (auf Postgresql hart) und auf einer 1M-Datensatztabelle. Diese Lösung benötigt 1,7s gegenüber 44s, die von der mit LEFT JOIN erstellt wurden. In meinem Fall hatte ich den corrispondant Ihres filtern Namen Feld gegen NULL - Werte, was zu noch besseren Leistungen um 0,2 Sekunden
quelle
Wenn die Leistung wirklich Ihr Anliegen ist, können Sie eine neue Spalte in der Tabelle
IsLastInGroup
vom Typ BIT einfügen.Setzen Sie es in den letzten Spalten auf true und pflegen Sie es bei jedem Einfügen / Aktualisieren / Löschen von Zeilen. Das Schreiben wird langsamer sein, aber Sie profitieren vom Lesen. Dies hängt von Ihrem Anwendungsfall ab und ich empfehle es nur, wenn Sie sich auf das Lesen konzentrieren.
Ihre Anfrage sieht also folgendermaßen aus:
quelle
quelle
Sie können durch Zählen gruppieren und erhalten auch das letzte Element der Gruppe wie:
quelle
Hoffe, dass die folgende Oracle-Abfrage helfen kann:
quelle
Ein anderer Ansatz :
Finden Sie die Eigenschaft mit dem maximalen m2_Preis für jedes Programm (n Eigenschaften in 1 Programm):
quelle