Wie der Titel schon sagt, möchte ich die erste Zeile jeder Reihe von Zeilen auswählen, die mit a gruppiert sind GROUP BY
.
Insbesondere, wenn ich eine purchases
Tabelle habe, die so aussieht:
SELECT * FROM purchases;
Mein Output:
id | Kunde | gesamt --- + ---------- + ------ 1 | Joe | 5 2 | Sally | 3 3 | Joe | 2 4 | Sally | 1
Ich möchte nach id
dem größten Kauf ( total
) fragen, den jeder getätigt hat customer
. Etwas wie das:
SELECT FIRST(id), customer, FIRST(total)
FROM purchases
GROUP BY customer
ORDER BY total DESC;
Erwartete Ausgabe:
ERSTE (id) | Kunde | ERSTE (insgesamt) ---------- + ---------- + ------------- 1 | Joe | 5 2 | Sally | 3
sql
sqlite
postgresql
group-by
greatest-n-per-group
David Wolever
quelle
quelle
MAX(total)
?Antworten:
Unter Oracle 9.2+ (nicht 8i + wie ursprünglich angegeben), SQL Server 2005+, PostgreSQL 8.4+, DB2, Firebird 3.0+, Teradata, Sybase, Vertica:
Unterstützt von jeder Datenbank:
Aber Sie müssen Logik hinzufügen, um Bindungen zu lösen:
quelle
ROW_NUMBER() OVER(PARTITION BY [...])
Zusammen mit einigen anderen Optimierungen konnte ich eine Abfrage von 30 Sekunden auf einige Millisekunden reduzieren. Vielen Dank! (PostgreSQL 9.2)total
für einen Kunden gleich hoch ist, gibt die erste Abfrage einen beliebigen Gewinner zurück (abhängig von den Implementierungsdetails; diesid
kann sich bei jeder Ausführung ändern!). Normalerweise (nicht immer) möchten Sie eine Zeile pro Kunde, definiert durch zusätzliche Kriterien wie "die mit der kleinstenid
". Zum Behebenid
an dieORDER BY
Liste von anhängenrow_number()
. Dann erhalten Sie das gleiche Ergebnis wie bei der 2. Abfrage, was für diesen Fall sehr ineffizient ist. Außerdem benötigen Sie für jede weitere Spalte eine weitere Unterabfrage.In PostgreSQL ist dies normalerweise einfacher und schneller (weitere Leistungsoptimierung siehe unten):
Oder kürzer (wenn nicht so klar) mit Ordnungszahlen der Ausgabespalten:
Wenn
total
kann NULL sein (wird in beiden Fällen nicht schaden, aber Sie möchten vorhandene Indizes abgleichen ):Hauptpunkte
DISTINCT ON
ist eine PostgreSQL-Erweiterung des Standards (wobei nurDISTINCT
die gesamteSELECT
Liste definiert ist).Listen Sie eine beliebige Anzahl von Ausdrücken in der
DISTINCT ON
Klausel auf. Der kombinierte Zeilenwert definiert Duplikate. Das Handbuch:Meine kühne Betonung.
DISTINCT ON
kann mit kombiniert werdenORDER BY
. Führende Ausdrücke inORDER BY
müssen in der Menge der Ausdrücke in enthalten seinDISTINCT ON
, aber Sie können die Reihenfolge zwischen diesen frei ändern. Beispiel. Sie können zusätzliche Ausdrücke hinzufügenORDER BY
, um eine bestimmte Zeile aus jeder Gruppe von Peers auszuwählen. Oder, wie es im Handbuch heißt :Ich habe
id
als letzten Punkt hinzugefügt , um die Verbindung zu lösen:"Wählen Sie die Zeile mit der kleinsten
id
aus jeder Gruppe, die die höchste teilttotal
."Um die Ergebnisse so zu ordnen, dass sie nicht mit der Sortierreihenfolge übereinstimmen, die die erste pro Gruppe bestimmt, können Sie die obige Abfrage in einer äußeren Abfrage mit einer anderen verschachteln
ORDER BY
. Beispiel.Wenn
total
NULL sein kann, möchten Sie höchstwahrscheinlich die Zeile mit dem größten Wert ungleich Null. Fügen SieNULLS LAST
wie gezeigt hinzu. Sehen:Die
SELECT
Liste ist nicht durch Ausdrücke in eingeschränktDISTINCT ON
oderORDER BY
in irgendeiner Weise. (Wird im obigen einfachen Fall nicht benötigt):Sie müssen keinen der Ausdrücke in
DISTINCT ON
oder einfügenORDER BY
.Sie können jeden anderen Ausdruck in die
SELECT
Liste aufnehmen. Dies ist hilfreich, um viel komplexere Abfragen durch Unterabfragen und Aggregat- / Fensterfunktionen zu ersetzen.Ich habe mit Postgres-Versionen 8.3 - 12 getestet. Aber die Funktion ist mindestens seit Version 7.1 vorhanden, also im Grunde immer.
Index
Der perfekte Index für die obige Abfrage wäre ein mehrspaltiger Index , der alle drei Spalten in übereinstimmender Reihenfolge und mit übereinstimmender Sortierreihenfolge umfasst:
Kann zu spezialisiert sein. Verwenden Sie es jedoch, wenn die Leseleistung für die jeweilige Abfrage von entscheidender Bedeutung ist. Wenn Sie
DESC NULLS LAST
in der Abfrage haben, verwenden Sie dasselbe im Index, damit die Sortierreihenfolge übereinstimmt und der Index anwendbar ist.Effektivität / Leistungsoptimierung
Wägen Sie Kosten und Nutzen ab, bevor Sie für jede Abfrage maßgeschneiderte Indizes erstellen. Das Potenzial des obigen Index hängt weitgehend von der Datenverteilung ab .
Der Index wird verwendet, weil er vorsortierte Daten liefert. In Postgres 9.2 oder höher kann die Abfrage auch nur dann von einem Index-Scan profitieren, wenn der Index kleiner als die zugrunde liegende Tabelle ist. Der Index muss jedoch vollständig gescannt werden.
Für wenige Zeilen pro Kunde (hohe Kardinalität in der Spalte
customer
) ist dies sehr effizient. Dies gilt umso mehr, wenn Sie ohnehin eine sortierte Ausgabe benötigen. Der Nutzen verringert sich mit einer wachsenden Anzahl von Zeilen pro Kunde.Im Idealfall haben Sie genug Zeit
work_mem
, um den beteiligten Sortierschritt im RAM zu verarbeiten und nicht auf die Festplatte zu übertragen. Im Allgemeinen kann einework_mem
zu hohe Einstellung jedoch nachteilige Auswirkungen haben. Berücksichtigen SieSET LOCAL
außergewöhnlich große Abfragen. Finden Sie heraus, wie viel Sie brauchenEXPLAIN ANALYZE
. Die Erwähnung von " Disk: " im Sortierschritt weist auf die Notwendigkeit von mehr hin:Für viele Zeilen pro Kunde (geringe Kardinalität in der Spalte
customer
) wäre ein loser Index-Scan (auch als "Skip-Scan" bezeichnet) (viel) effizienter, dies ist jedoch bis Postgres 12 nicht implementiert. (Eine Implementierung für Nur-Index-Scans ist in Entwicklung für Postgres 13. Siehe hier und hier .)Derzeit gibt es schnellere Abfragetechniken , um dies zu ersetzen. Insbesondere, wenn Sie einen separaten Tisch mit eindeutigen Kunden haben, was der typische Anwendungsfall ist. Aber auch wenn Sie nicht:
Benchmark
Ich hatte hier einen einfachen Benchmark, der mittlerweile veraltet ist. Ich habe es in dieser separaten Antwort durch einen detaillierten Benchmark ersetzt .
quelle
DISTINCT ON
extrem langsam werden, wenn Sie sich nähern . Die Implementierung sortiert immer die gesamte Tabelle und durchsucht sie nach Duplikaten, wobei alle Indizes ignoriert werden (auch wenn Sie den erforderlichen mehrspaltigen Index erstellt haben). Eine mögliche Lösung finden Sie unter explainextended.com/2009/05/03/postgresql-optimizing-distinct .SELECT
Liste nützlich sein .DISTINCT ON
ist nur gut, um eine Zeile pro Gruppe von Peers zu bekommen.Benchmark
Testen der interessantesten Kandidaten mit Postgres 9.4 und 9.5 mit einer halbwegs realistischen Tabelle von 200.000 Zeilen in
purchases
und 10.000 unterschiedlichencustomer_id
( durchschnittlich 20 Zeilen pro Kunde ).Für Postgres 9.5 habe ich einen zweiten Test mit effektiv 86446 verschiedenen Kunden durchgeführt. Siehe unten ( durchschnittlich 2,3 Zeilen pro Kunde ).
Installieren
Haupttisch
Ich verwende eine
serial
(PK-Einschränkung unten hinzugefügt) und eine Ganzzahl,customer_id
da dies ein typischeres Setup ist. Wird auch hinzugefügtsome_column
, um normalerweise mehr Spalten auszugleichen.Dummy-Daten, PK, Index - eine typische Tabelle enthält auch einige tote Tupel:
customer
Tabelle - für übergeordnete AbfrageIn meinem zweiten Test für 9.5 habe ich das gleiche Setup verwendet, aber mit
random() * 100000
zu generierencustomer_id
, um nur wenige Zeilen pro zu erhaltencustomer_id
.Objektgrößen für Tabelle
purchases
Mit dieser Abfrage generiert .
Abfragen
1.
row_number()
in CTE ( siehe andere Antwort )2.
row_number()
in Unterabfrage (meine Optimierung)3.
DISTINCT ON
( siehe andere Antwort )4. rCTE mit
LATERAL
Unterabfrage ( siehe hier )5.
customer
Tabelle mitLATERAL
( siehe hier )6.
array_agg()
mitORDER BY
( siehe andere Antwort )Ergebnisse
Ausführungszeit für die oben genannten Abfragen mit
EXPLAIN ANALYZE
(und allen Optionen deaktiviert ), am besten aus 5 Läufen .Alle Abfragen verwendeten eine Nur - Indexsuche auf
purchases2_3c_idx
(unter anderen Stufen). Einige von ihnen nur für die kleinere Größe des Index, andere effektiver.A. Postgres 9.4 mit 200k Reihen und ~ 20 pro
customer_id
B. Das gleiche gilt für Postgres 9.5
C. Wie B., jedoch mit ~ 2,3 Zeilen pro
customer_id
Verwandte Benchmarks
Hier ist ein neuer Test von "ogr" mit 10 Millionen Zeilen und 60.000 einzigartigen "Kunden" auf Postgres 11.5 (Stand: September 2019). Die Ergebnisse stimmen immer noch mit dem überein, was wir bisher gesehen haben:
Ursprünglicher (veralteter) Benchmark von 2011
Ich habe drei Tests mit PostgreSQL 9.1 in einer realen Tabelle mit 65579 Zeilen und einspaltigen btree-Indizes für jede der drei beteiligten Spalten durchgeführt und die beste Ausführungszeit von 5 Läufen genommen.
Vergleich der ersten Abfrage (
A
) von @OMGPonies mit der obigenDISTINCT ON
Lösung (B
):Wählen Sie die gesamte Tabelle aus. In diesem Fall werden 5958 Zeilen angezeigt.
Verwenden Sie die Bedingung,
WHERE customer BETWEEN x AND y
die zu 1000 Zeilen führt.Wählen Sie einen einzelnen Kunden mit
WHERE customer = x
.Der gleiche Test wurde mit dem in der anderen Antwort beschriebenen Index wiederholt
quelle
2. row_number()
und5. customer table with LATERAL
Beispiele, welche die ID der kleinste wird nicht gewährleistet?customer_id
Zeile mit der höchsten abzurufentotal
. Es ist ein irreführender Zufall in den Testdaten der Frage, dass dieid
in den ausgewählten Zeilen zufällig auch die kleinste pro istcustomer_id
.Das ist üblich Größte-n-pro-GruppeProblem, das bereits gut getestete und hochoptimierte Lösungen hat . Persönlich bevorzuge ich die Left Join-Lösung von Bill Karwin (der ursprüngliche Beitrag mit vielen anderen Lösungen ).
Beachten Sie, dass eine Reihe von Lösungen für dieses häufig auftretende Problem überraschenderweise in einer der offiziellsten Quellen, dem MySQL-Handbuch, zu finden sind ! Siehe Beispiele für häufig verwendete Abfragen: Die Zeilen, die das gruppenweise Maximum einer bestimmten Spalte enthalten .
quelle
DISTINCT ON
Version viel kürzer, einfacher und bietet in Postgres im Allgemeinen eine bessere Leistung als Alternativen mit einem Self-LEFT JOIN
oder Semi-Anti-Join mitNOT EXISTS
. Es ist auch "gut getestet".In Postgres können Sie Folgendes verwenden
array_agg
:Dies gibt Ihnen den
id
größten Einkauf jedes Kunden.Einige Dinge zu beachten:
array_agg
ist eine Aggregatfunktion, mit der es funktioniertGROUP BY
.array_agg
Mit dieser Option können Sie eine Reihenfolge angeben, die nur auf sich selbst beschränkt ist, damit die Struktur der gesamten Abfrage nicht eingeschränkt wird. Es gibt auch eine Syntax zum Sortieren von NULL-Werten, wenn Sie etwas anderes als die Standardeinstellung ausführen müssen.array_agg
auf ähnliche Weise für Ihre dritte Ausgabespalte verwenden, diesmax(total)
ist jedoch einfacher.DISTINCT ON
dazuarray_agg
können Sie mit verwendenGROUP BY
, falls Sie dies aus anderen Gründen möchten.quelle
Die Lösung ist, wie von Erwin erwähnt, aufgrund des Vorhandenseins von SubQs nicht sehr effizient
quelle
Ich benutze diesen Weg (nur postgresql): https://wiki.postgresql.org/wiki/First/last_%28aggregate%29
Dann sollte Ihr Beispiel fast so funktionieren wie es ist:
CAVEAT: Die NULL-Zeilen werden ignoriert
Bearbeiten 1 - Verwenden Sie stattdessen die Erweiterung postgres
Jetzt benutze ich diesen Weg: http://pgxn.org/dist/first_last_agg/
So installieren Sie auf Ubuntu 14.04:
Es ist eine Postgres-Erweiterung, die Ihnen erste und letzte Funktionen bietet. anscheinend schneller als der obige Weg.
Bearbeiten 2 - Bestellen und Filtern
Wenn Sie Aggregatfunktionen (wie diese) verwenden, können Sie die Ergebnisse ordnen, ohne dass die Daten bereits bestellt werden müssen:
Das äquivalente Beispiel für die Bestellung wäre also etwa:
Natürlich können Sie bestellen und filtern, wie Sie es für das Aggregat halten. Es ist eine sehr mächtige Syntax.
quelle
Die Abfrage:
WIE FUNKTIONIERT DAS! (Ich war dort)
Wir möchten sicherstellen, dass wir für jeden Einkauf nur die höchste Summe haben.
Einige theoretische Dinge (überspringen Sie diesen Teil, wenn Sie nur die Abfrage verstehen wollen)
Sei Total eine Funktion T (Kunde, ID), die einen Wert mit dem Namen und der ID zurückgibt. Um zu beweisen, dass die angegebene Summe (T (Kunde, ID)) die höchste ist, müssen wir beweisen, dass wir beides beweisen wollen
ODER
Der erste Ansatz erfordert, dass wir alle Datensätze für diesen Namen erhalten, die ich nicht wirklich mag.
Der zweite braucht eine kluge Methode, um zu sagen, dass es keinen höheren Datensatz als diesen geben kann.
Zurück zu SQL
Wenn wir die Tabelle mit dem Namen verlassen und die Summe kleiner als die verknüpfte Tabelle ist:
Wir stellen sicher, dass alle Datensätze, die einen anderen Datensatz mit der höheren Summe für denselben Benutzer haben, verbunden werden:
Dies hilft uns, bei jedem Einkauf nach der höchsten Gesamtsumme zu filtern, ohne dass eine Gruppierung erforderlich ist:
Und das ist die Antwort, die wir brauchen.
quelle
Sehr schnelle Lösung
und wirklich sehr schnell, wenn die Tabelle nach id indiziert ist:
quelle
In SQL Server können Sie dies tun:
Explaination: Hier Gruppe von auf der Grundlage von Kunden durchgeführt wird und bestellt es dann insgesamt dann jede solche Gruppe Seriennummern als die Gäste gegeben und wir nehmen aus ersten 1 Kunden , dessen Strank 1
quelle
Verwenden Sie die
ARRAY_AGG
Funktion für PostgreSQL , U-SQL , IBM DB2 und Google BigQuery SQL :quelle
In PostgreSQL besteht eine andere Möglichkeit darin, die
first_value
Fensterfunktion in Kombination zu verwenden mitSELECT DISTINCT
:Ich habe ein Composite erstellt
(id, total)
, sodass beide Werte von demselben Aggregat zurückgegeben werden. Sie können sich natürlich immerfirst_value()
zweimal bewerben .quelle
Die akzeptierte Lösung "Unterstützt von jeder Datenbank" von OMG Ponies hat eine gute Geschwindigkeit aus meinem Test.
Hier biete ich einen gleichen Ansatz, aber eine vollständigere und sauberere Lösung für jede Datenbank. Bindungen werden berücksichtigt (vorausgesetzt, Sie möchten nur eine Zeile für jeden Kunden erhalten, sogar mehrere Datensätze für die maximale Gesamtsumme pro Kunde), und andere Kauffelder (z. B. purchase_payment_id) werden für die tatsächlich übereinstimmenden Zeilen in der Kauftabelle ausgewählt.
Unterstützt von jeder Datenbank:
Diese Abfrage ist relativ schnell, insbesondere wenn ein zusammengesetzter Index wie (Kunde, Gesamt) in der Kauftabelle vorhanden ist.
Anmerkung:
t1, t2 sind Unterabfrage-Alias, die je nach Datenbank entfernt werden können.
Vorsichtsmaßnahme : Die
using (...)
Klausel wird derzeit in MS-SQL und Oracle db ab dieser Bearbeitung im Januar 2017 nicht unterstützt. Sie müssen sie selbst auf zon t2.id = purchase.id
. B. usw. erweitern. Die USING-Syntax funktioniert in SQLite, MySQL und PostgreSQL.quelle
Snowflake / Teradata unterstützt eine
QUALIFY
Klausel, die wieHAVING
bei Fensterfunktionen funktioniert:quelle
Wenn Sie eine Zeile (aufgrund Ihrer spezifischen Bedingung) aus der Gruppe der aggregierten Zeilen auswählen möchten.
Wenn Sie zusätzlich zu eine andere (
sum/avg
) Aggregationsfunktion verwenden möchtenmax/min
. Somit kann man keinen Hinweis mit verwendenDISTINCT ON
Sie können die nächste Unterabfrage verwenden:
Sie können durch eine
amount = MAX( tf.amount )
beliebige Bedingung mit einer Einschränkung ersetzen : Diese Unterabfrage darf nicht mehr als eine Zeile zurückgebenAber wenn Sie solche Dinge tun möchten, suchen Sie wahrscheinlich nach Fensterfunktionen
quelle
Für SQl Server ist der effizienteste Weg:
und vergessen Sie nicht, einen Clustered-Index für verwendete Spalten zu erstellen
quelle