PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Erste Zeile in jeder GROUP BY-Gruppe auswählen?

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 oder ORDER 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.