Ich habe es mit einer Postgres-Tabelle ("Lives" genannt) zu tun, die Datensätze mit Spalten für time_stamp, usr_id, transaction_id und living_remaining enthält. Ich benötige eine Abfrage, die mir für jede usr_id die aktuellste Lebenssumme gibt
- Es gibt mehrere Benutzer (verschiedene usr_id's)
- time_stamp ist keine eindeutige Kennung: Manchmal treten Benutzerereignisse (zeilenweise in der Tabelle) mit demselben time_stamp auf.
- trans_id ist nur für sehr kleine Zeitbereiche eindeutig: Im Laufe der Zeit wiederholt es sich
- verbleibende Leben (für einen bestimmten Benutzer) können im Laufe der Zeit sowohl zunehmen als auch abnehmen
Beispiel:
Zeitstempel | Leben bleibt | usr_id | trans_id ----------------------------------------- 07:00 | 1 | 1 | 1 09:00 | 4 | 2 | 2 10:00 | 2 | 3 | 3 10:00 | 1 | 2 | 4 11:00 | 4 | 1 | 5 11:00 | 3 | 1 | 6 13:00 | 3 | 3 | 1
Da ich für jede angegebene usr_id auf andere Spalten der Zeile mit den neuesten Daten zugreifen muss, benötige ich eine Abfrage, die ein Ergebnis wie das folgende liefert:
Zeitstempel | Leben bleibt | usr_id | trans_id ----------------------------------------- 11:00 | 3 | 1 | 6 10:00 | 1 | 2 | 4 13:00 | 3 | 3 | 1
Wie bereits erwähnt, kann jede usr_id Leben gewinnen oder verlieren, und manchmal treten diese Ereignisse mit Zeitstempel so nahe beieinander auf, dass sie denselben Zeitstempel haben! Daher funktioniert diese Abfrage nicht:
SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM
(SELECT usr_id, max(time_stamp) AS max_timestamp
FROM lives GROUP BY usr_id ORDER BY usr_id) a
JOIN lives b ON a.max_timestamp = b.time_stamp
Stattdessen muss ich sowohl time_stamp (erste) als auch trans_id (zweite) verwenden, um die richtige Zeile zu identifizieren. Ich muss diese Informationen dann auch von der Unterabfrage an die Hauptabfrage übergeben, die die Daten für die anderen Spalten der entsprechenden Zeilen bereitstellt. Dies ist die gehackte Abfrage, die ich zur Arbeit gebracht habe:
SELECT b.time_stamp,b.lives_remaining,b.usr_id,b.trans_id FROM
(SELECT usr_id, max(time_stamp || '*' || trans_id)
AS max_timestamp_transid
FROM lives GROUP BY usr_id ORDER BY usr_id) a
JOIN lives b ON a.max_timestamp_transid = b.time_stamp || '*' || b.trans_id
ORDER BY b.usr_id
Okay, das funktioniert, aber ich mag es nicht. Es erfordert eine Abfrage innerhalb einer Abfrage, einen Self-Join, und es scheint mir, dass es viel einfacher sein könnte, wenn Sie die Zeile abrufen, die MAX mit dem größten Zeitstempel und der größten trans_id gefunden hat. Die Tabelle "lebt" enthält zig Millionen zu analysierende Zeilen. Daher möchte ich, dass diese Abfrage so schnell und effizient wie möglich ist. Ich bin insbesondere bei RDBM und Postgres neu, daher weiß ich, dass ich die richtigen Indizes effektiv nutzen muss. Ich bin ein bisschen verloren, wie man optimiert.
Ich habe hier eine ähnliche Diskussion gefunden . Kann ich eine Art von Postgres ausführen, die einer Oracle-Analysefunktion entspricht?
Alle Ratschläge zum Zugriff auf verwandte Spalteninformationen, die von einer Aggregatfunktion (wie MAX) verwendet werden, zum Erstellen von Indizes und zum Erstellen besserer Abfragen sind sehr willkommen!
PS Sie können Folgendes verwenden, um meinen Beispielfall zu erstellen:
create TABLE lives (time_stamp timestamp, lives_remaining integer,
usr_id integer, trans_id integer);
insert into lives values ('2000-01-01 07:00', 1, 1, 1);
insert into lives values ('2000-01-01 09:00', 4, 2, 2);
insert into lives values ('2000-01-01 10:00', 2, 3, 3);
insert into lives values ('2000-01-01 10:00', 1, 2, 4);
insert into lives values ('2000-01-01 11:00', 4, 1, 5);
insert into lives values ('2000-01-01 11:00', 3, 1, 6);
insert into lives values ('2000-01-01 13:00', 3, 3, 1);
quelle
MAX
BY
2 Spalten bekommt !Antworten:
In einer Tabelle mit 158.000 Pseudozufallszeilen (usr_id gleichmäßig zwischen 0 und 10.000 verteilt)
trans_id
gleichmäßig zwischen 0 und 30 verteilt),Unter Abfragekosten beziehe ich mich unten auf die Kostenschätzung des kostenbasierten Optimierers von Postgres (mit den Standardwerten von Postgres
xxx_cost
), bei der es sich um eine gewichtete Funktionsschätzung der erforderlichen E / A- und CPU-Ressourcen handelt. Sie können dies erreichen, indem Sie PgAdminIII starten und "Query / Explain (F7)" für die Abfrage ausführen, wobei "Query / Explain options" auf "Analyze" gesetzt ist.usr_id
,trans_id
,time_stamp
))usr_id
,trans_id
)).usr_id
,trans_id
,time_stamp
))usr_id
,EXTRACT(EPOCH FROM time_stamp)
,trans_id
))usr_id
,time_stamp
,trans_id
)); Es hat den Vorteil, dass dielives
Tabelle nur einmal gescannt wird. Wenn Sie work_mem vorübergehend erhöhen (falls erforderlich) , um die Sortierung im Speicher zu berücksichtigen , ist es bei weitem die schnellste aller Abfragen.Alle oben genannten Zeiten beinhalten das Abrufen der vollständigen Ergebnismenge von 10.000 Zeilen.
Ihr Ziel ist eine minimale Kostenschätzung und eine minimale Ausführungszeit für Abfragen, wobei der Schwerpunkt auf den geschätzten Kosten liegt. Die Ausführung von Abfragen kann erheblich von den Laufzeitbedingungen abhängen (z. B. ob relevante Zeilen bereits vollständig im Speicher zwischengespeichert sind oder nicht), während dies bei der Kostenschätzung nicht der Fall ist. Denken Sie andererseits daran, dass die Kostenschätzung genau das ist, eine Schätzung.
Die beste Ausführungszeit für Abfragen wird erzielt, wenn eine dedizierte Datenbank ohne Last ausgeführt wird (z. B. Spielen mit pgAdminIII auf einem Entwicklungs-PC). Die Abfragezeit variiert in der Produktion basierend auf der tatsächlichen Maschinenlast / Datenzugriffsverteilung. Wenn eine Abfrage etwas schneller (<20%) als die andere erscheint, aber viel höhere Kosten verursacht, ist es im Allgemeinen klüger, die mit höherer Ausführungszeit und geringeren Kosten auszuwählen.
Wenn Sie erwarten, dass zum Zeitpunkt der Ausführung der Abfrage keine Konkurrenz um den Speicher auf Ihrem Produktionscomputer besteht (z. B. werden der RDBMS-Cache und der Dateisystem-Cache nicht durch gleichzeitige Abfragen und / oder Dateisystemaktivitäten überlastet), dann die Abfragezeit, die Sie erhalten haben im Standalone-Modus (z. B. pgAdminIII auf einem Entwicklungs-PC) ist repräsentativ. Wenn das Produktionssystem in Konflikt gerät, verschlechtert sich die Abfragezeit proportional zum geschätzten Kostenverhältnis, da die Abfrage mit den niedrigeren Kosten nicht so stark vom Cache abhängt, während die Abfrage mit den höheren Kosten dieselben Daten immer wieder überprüft (Auslösen) zusätzliche E / A in Abwesenheit eines stabilen Caches), z.
Vergessen Sie nicht,
ANALYZE lives
nach dem Erstellen der erforderlichen Indizes einmal auszuführen .Abfrage Nr. 1
Abfrage Nr. 2
Update 2013/01/29
Ab Version 8.4 unterstützt Postgres die Fensterfunktion , sodass Sie etwas schreiben können, das so einfach und effizient ist wie:
Abfrage Nr. 3
quelle
Ich würde eine saubere Version vorschlagen, die auf
DISTINCT ON
(siehe Dokumente ) basiert :quelle
Hier ist eine andere Methode, die zufällig keine korrelierten Unterabfragen oder GROUP BY verwendet. Ich bin kein Experte für PostgreSQL-Leistungsoptimierung, daher schlage ich vor, dass Sie sowohl diese als auch die von anderen Leuten angegebenen Lösungen ausprobieren, um herauszufinden, welche für Sie besser funktioniert.
Ich gehe davon aus, dass dies
trans_id
zumindest über einen bestimmten Wert von eindeutig isttime_stamp
.quelle
Ich mag den Stil von Mike Woodhouses Antwort auf der anderen Seite, die Sie erwähnt haben. Es ist besonders prägnant , wenn die Sache immer maximiert wird , ist nur eine einzige Spalte, wobei in diesem Fall die Unterabfrage nur verwenden kann ,
MAX(some_col)
undGROUP BY
die anderen Spalten, aber in Ihrem Fall haben Sie eine 2-Teilmenge maximiert werden, können Sie immer noch so tun , indem SieORDER BY
PlusLIMIT 1
stattdessen (wie von Quassnoi gemacht):Ich finde die Verwendung der
WHERE (a, b, c) IN (subquery)
Zeilenkonstruktorsyntax hilfreich, da dadurch weniger Wortschatz benötigt wird.quelle
Actaully gibt es eine hackige Lösung für dieses Problem. Angenommen, Sie möchten den größten Baum jedes Waldes in einer Region auswählen.
Wenn Sie Bäume nach Wäldern gruppieren, wird eine unsortierte Liste von Bäumen angezeigt, und Sie müssen den größten finden. Als erstes sollten Sie die Zeilen nach ihrer Größe sortieren und die erste Ihrer Liste auswählen. Es mag ineffizient erscheinen, aber wenn Sie Millionen von Zeilen haben, ist es ziemlich schneller als die Lösungen, die
JOIN
s undWHERE
Bedingungen enthalten.Übrigens, beachten Sie, dass
ORDER_BY
forarray_agg
in Postgresql 9.0 eingeführt wirdquelle
SELECT usr_id, (array_agg(time_stamp ORDER BY time_stamp DESC))[1] AS timestamp, (array_agg(lives_remaining ORDER BY time_stamp DESC))[1] AS lives_remaining, (array_agg(trans_id ORDER BY time_stamp DESC))[1] AS trans_id FROM lives GROUP BY usr_id
In Postgressql 9.5 gibt es eine neue Option namens DISTINCT ON
Es eliminiert doppelte Zeilen und lässt nur die erste Zeile übrig, wie in der ORDER BY-Klausel definiert.
siehe die offizielle Dokumentation
quelle
Durch das Erstellen eines Index für
(usr_id, time_stamp, trans_id)
wird diese Abfrage erheblich verbessert.Sie sollten immer, immer eine Art
PRIMARY KEY
in Ihren Tabellen haben.quelle
Ich denke, Sie haben hier ein großes Problem: Es gibt keinen monoton ansteigenden "Zähler", der garantiert, dass eine bestimmte Zeile später als eine andere passiert ist. Nehmen Sie dieses Beispiel:
Aus diesen Daten können Sie nicht ermitteln, welcher Eintrag der letzte ist. Ist es der zweite oder der letzte? Es gibt keine sort- oder max () -Funktion, die Sie auf diese Daten anwenden können, um die richtige Antwort zu erhalten.
Das Erhöhen der Auflösung des Zeitstempels wäre eine große Hilfe. Da das Datenbankmodul Anforderungen serialisiert, können Sie bei ausreichender Auflösung sicherstellen, dass keine zwei Zeitstempel gleich sind.
Alternativ können Sie eine trans_id verwenden, die sich sehr, sehr lange nicht verlängert. Wenn Sie eine trans_id haben, die überrollt, können Sie (für denselben Zeitstempel) nicht feststellen, ob trans_id 6 aktueller als trans_id 1 ist, es sei denn, Sie führen komplizierte Berechnungen durch.
quelle
Eine andere Lösung, die Sie vielleicht nützlich finden.
quelle