DISTINCT ON
ist in der Regel am einfachsten und schnellsten in PostgreSQL .
(Zur Leistungsoptimierung für bestimmte Workloads siehe unten.)
SELECT DISTINCT ON (customer)
id, customer, total
FROM purchases
ORDER BY customer, total DESC, id;
Oder kürzer (wenn nicht so klar) mit Ordnungszahlen der Ausgabespalten:
SELECT DISTINCT ON (2)
id, customer, total
FROM purchases
ORDER BY 2, 3 DESC, 1;
Wenn total
kann NULL sein (wird so oder so nicht schaden, aber Sie sollten vorhandene Indizes abgleichen):
...
ORDER BY customer, total DESC NULLS LAST, id;
Wichtige Punkte
DISTINCT ON
ist eine PostgreSQL-Erweiterung des Standards (wobei nur DISTINCT
insgesamt SELECT
Liste ist definiert).
Listen Sie eine beliebige Anzahl von Ausdrücken im DISTINCT ON
auf -Klausel definiert der kombinierte Zeilenwert Duplikate. Das Handbuch:
Offensichtlich werden zwei Zeilen als verschieden betrachtet, wenn sie sich in mindestens einem Spaltenwert unterscheiden. Nullwerte werden in diesem Vergleich als gleich angesehen.
Fettdruck von mir.
DISTINCT ON
kombinierbar mit ORDER BY
. Führende Ausdrücke in ORDER BY
muss in der Menge der Ausdrücke in DISTINCT ON
enthalten sein , aber Sie können die Reihenfolge zwischen diesen frei ändern. Beispiel.
Sie können zusätzliche hinzufügen Ausdrücke zu ORDER BY
eine bestimmte Zeile aus jeder Gruppe von Peers auszuwählen. Oder, wie es im Handbuch heißt:
Das DISTINCT ON
Ausdruck(e) müssen mit ORDER BY
ganz links übereinstimmen Ausdruck(e). Der ORDER BY
-Klausel enthält normalerweise zusätzliche Ausdrücke, die die gewünschte Priorität von Zeilen innerhalb jedes DISTINCT ON
bestimmen Gruppe.
Ich habe id
hinzugefügt als letztes Element, um Bindungen zu lösen:
"Wählen Sie die Zeile mit der kleinsten id
aus jeder Gruppe mit der höchsten total
."
Um Ergebnisse in einer Weise zu ordnen, die nicht mit der Sortierreihenfolge übereinstimmt, die die erste pro Gruppe bestimmt, können Sie die obere Abfrage in einer äußeren Abfrage mit einem weiteren ORDER BY
verschachteln . Beispiel.
Wenn total
kann NULL sein, Sie höchstwahrscheinlich wollen die Zeile mit dem größten Nicht-Null-Wert. Fügen Sie NULLS LAST
hinzu wie demonstriert. Siehe:
- Nach Spalte ASC sortieren, aber zuerst NULL-Werte?
Das SELECT
Liste wird nicht durch Ausdrücke in DISTINCT ON
eingeschränkt oder ORDER BY
in irgendeiner Weise. (Im obigen einfachen Fall nicht erforderlich):
-
Sie müssen nicht Fügen Sie einen der Ausdrücke in
DISTINCT ON
ein oderORDER BY
. -
Sie können Fügen Sie jeden anderen Ausdruck in
SELECT
ein aufführen. Dies ist hilfreich, um viel komplexere Abfragen durch Unterabfragen und Aggregat- / Fensterfunktionen zu ersetzen.
Getestet habe ich mit den Postgres-Versionen 8.3 – 13. Aber das Feature ist mindestens seit Version 7.1 da, also eigentlich immer.
Index
Das perfekte index für die obige Abfrage wäre ein mehrspaltiger Index, der alle drei Spalten in übereinstimmender Reihenfolge und mit übereinstimmender Sortierreihenfolge umfasst:
CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);
Kann zu spezialisiert sein. Verwenden Sie es jedoch, wenn die Leseleistung für die jeweilige Abfrage entscheidend ist. Wenn Sie DESC NULLS LAST
haben Verwenden Sie in der Abfrage dasselbe im Index, damit die Sortierreihenfolge übereinstimmt und der Index anwendbar ist.
Effektivität / Leistungsoptimierung
Wägen Sie Kosten und Nutzen ab, bevor Sie maßgeschneiderte Indizes für jede Abfrage 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 von einem Nur-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 Spalte customer
), das ist sehr effizient. Umso mehr, wenn Sie ohnehin eine sortierte Ausgabe benötigen. Der Nutzen schrumpft mit steigender Zeilenzahl pro Kunde.
Idealerweise haben Sie genügend work_mem
um den betreffenden Sortierschritt im RAM zu verarbeiten und nicht auf die Festplatte zu übertragen. Aber generell das Setzen von work_mem
auch hoch kann nachteilige Auswirkungen haben. Betrachten Sie SET LOCAL
für außergewöhnlich große Abfragen. Finden Sie mit EXPLAIN ANALYZE
heraus, wie viel Sie benötigen . Erwähnung von "Datenträger: " im Sortierschritt zeigt an, dass mehr benötigt wird:
- Konfigurationsparameter work_mem in PostgreSQL unter Linux
- Optimieren Sie einfache Abfragen mit ORDER BY Datum und Text
Für viele Zeilen pro Kunde (niedrige Kardinalität in Spalte customer
), ein loser Index-Scan (a.k.a. „skip scan“) wäre (viel) effizienter, aber das ist bis Postgres 14 nicht implementiert. (Eine Implementierung für Index-only-Scans ist für Postgres 15 in Entwicklung. Siehe hier und hier.) jetzt gibt es schnellere Abfragetechniken diese zu ersetzen. Insbesondere, wenn Sie eine separate Tabelle mit eindeutigen Kunden haben, was der typische Anwendungsfall ist. Aber auch wenn nicht:
- SELECT DISTINCT ist langsamer als erwartet auf meiner Tabelle in PostgreSQL
- GRUPPE NACH-Abfrage optimieren, um die neueste Zeile pro Benutzer abzurufen
- Gruppenweise maximale Abfrage optimieren
- Letzte N zugehörige Zeilen pro Zeile abfragen
Benchmarks
Siehe separate Antwort.