Es kommt sehr auf die Umstände und die genauen Anforderungen an. Betrachten Sie meinen Kommentar zur Frage .
Einfache lösung
Mit DISTINCT ON
in Postgres:
SELECT DISTINCT ON (i.good, i.the_date)
i.the_date, p.the_date AS pricing_date, i.good, p.price
FROM inventory i
LEFT JOIN price p ON i.good = p.good AND i.the_date >= p.the_date
ORDER BY i.good, i.the_date, p.the_date DESC;
Geordnetes Ergebnis.
Oder mit NOT EXISTS
Standard-SQL (funktioniert mit jedem mir bekannten RDBMS):
SELECT i.the_date, p.the_date AS pricing_date, i.good, i.quantity, p.price
FROM inventory i
LEFT JOIN price p ON p.good = i.good AND p.the_date <= i.the_date
WHERE NOT EXISTS (
SELECT 1 FROM price p1
WHERE p1.good = p.good
AND p1.the_date <= i.the_date
AND p1.the_date > p.the_date
);
Gleiches Ergebnis, aber mit beliebiger Sortierreihenfolge - außer Sie fügen hinzu ORDER BY
.
Abhängig von der Datenverteilung, den genauen Anforderungen und den Indizes kann einer davon schneller sein.
Im Allgemeinen DISTINCT ON
ist der Sieger und Sie erhalten ein sortiertes Ergebnis darüber. In bestimmten Fällen sind andere Abfragetechniken jedoch (viel) schneller. Siehe unten.
Lösungen mit Unterabfragen zur Berechnung von Max / Min-Werten sind im Allgemeinen langsamer. Varianten mit CTEs sind im Allgemeinen noch langsamer.
Einfache Ansichten (wie in einer anderen Antwort vorgeschlagen) tragen in Postgres überhaupt nicht zur Leistung bei.
SQL-Geige.
Richtige Lösung
Zeichenfolgen und Kollatierung
Zuallererst leiden Sie unter einem suboptimalen Tabellenlayout. Es mag trivial erscheinen, aber die Normalisierung Ihres Schemas kann sehr weit gehen.
Sortierung nach Zeichentypen ( text
, varchar
, ...) werden muss , erfolgt nach dem locale - der COLLATION im Besonderen. Höchstwahrscheinlich verwendet Ihre Datenbank einige lokale Regeln (wie in meinem Fall:) de_AT.UTF-8
. Finden Sie es heraus mit:
SHOW lc_collate;
Dadurch werden das Sortieren und die Indexsuche verlangsamt . Je länger Ihre Saiten (Warennamen) sind, desto schlechter. Wenn Sie die Kollatierungsregeln in Ihrer Ausgabe (oder die Sortierreihenfolge überhaupt) nicht beachten, kann dies schneller sein, wenn Sie Folgendes hinzufügen COLLATE "C"
:
SELECT DISTINCT ON (i.good COLLATE "C", i.the_date)
i.the_date, p.the_date AS pricing_date, i.good, p.price
FROM inventory i
LEFT JOIN price p ON i.good = p.good AND i.the_date >= p.the_date
ORDER BY i.good COLLATE "C", i.the_date, p.the_date DESC;
Beachten Sie, wie ich die Kollatierung an zwei Stellen hinzugefügt habe.
In meinem Test doppelt so schnell mit jeweils 20.000 Zeilen und sehr einfachen Namen ('good123').
Index
Wenn Ihre Abfrage einen Index verwenden soll, müssen Spalten mit Zeichendaten eine übereinstimmende Sortierung verwenden ( good
im Beispiel):
CREATE INDEX inventory_good_date_desc_collate_c_idx
ON price(good COLLATE "C", the_date DESC);
Lesen Sie unbedingt die letzten beiden Kapitel dieser Antwort zu SO:
Sie können sogar mehrere Indizes mit unterschiedlichen Sortierungen in denselben Spalten haben - wenn Sie in anderen Abfragen auch Waren benötigen, die nach einer anderen (oder der Standard-) Sortierung sortiert sind.
Normalisieren
Redundante Zeichenfolgen (name of good) belasten auch Ihre Tabellen und Indizes, wodurch alles noch langsamer wird. Mit einem korrekten Tabellenlayout könnten Sie die meisten Probleme zunächst vermeiden. Könnte so aussehen:
CREATE TABLE good (
good_id serial PRIMARY KEY
, good text NOT NULL
);
CREATE TABLE inventory (
good_id int REFERENCES good (good_id)
, the_date date NOT NULL
, quantity int NOT NULL
, PRIMARY KEY(good_id, the_date)
);
CREATE TABLE price (
good_id int REFERENCES good (good_id)
, the_date date NOT NULL
, price numeric NOT NULL
, PRIMARY KEY(good_id, the_date));
Die Primärschlüssel liefern automatisch (fast) alle benötigten Indizes.
Je nach fehlenden Details, einen mehrspaltigen Index auf price
mit absteigender Reihenfolge auf der zweiten Spalte kann die Leistung verbessern:
CREATE INDEX price_good_date_desc_idx ON price(good, the_date DESC);
Auch hier muss die Sortierung mit Ihrer Suchanfrage übereinstimmen (siehe oben).
In Postgres 9.2 oder höher kann das "Abdecken von Indizes" für Index-Only-Scans noch weiter helfen - insbesondere, wenn Ihre Tabellen zusätzliche Spalten enthalten und die Tabelle somit wesentlich größer als der abdeckende Index ist.
Diese resultierenden Abfragen sind viel schneller:
EXISTIERT NICHT
SELECT i.the_date, p.the_date AS pricing_date, g.good, i.quantity, p.price
FROM inventory i
JOIN good g USING (good_id)
LEFT JOIN price p ON p.good_id = i.good_id AND p.the_date <= i.the_date
AND NOT EXISTS (
SELECT 1 FROM price p1
WHERE p1.good_id = p.good_id
AND p1.the_date <= i.the_date
AND p1.the_date > p.the_date
);
DISTINCT ON
SELECT DISTINCT ON (i.the_date)
i.the_date, p.the_date AS pricing_date, g.good, i.quantity, p.price
FROM inventory i
JOIN good g USING (good_id)
LEFT JOIN price p ON p.good_id = i.good_id AND p.the_date <= i.the_date
ORDER BY i.the_date, p.the_date DESC;
SQL-Geige.
Schnellere Lösungen
Wenn das immer noch nicht schnell genug ist, kann es schnellere Lösungen geben.
Rekursiver CTE / JOIN LATERAL
/ korrelierte Unterabfrage
Speziell für Datenverteilungen mit vielen Preisen pro Ware :
Materialisierte Ansicht
Wenn Sie dies häufig und schnell ausführen müssen, sollten Sie eine materialisierte Ansicht erstellen. Ich denke, es ist davon auszugehen, dass sich Preise und Lagerbestände für vergangene Daten selten ändern. Berechnen Sie das Ergebnis einmal und speichern Sie einen Schnappschuss als materialisierte Ansicht.
Postgres 9.3+ bietet automatisierte Unterstützung für materialisierte Ansichten. Sie können eine Basisversion problemlos in älteren Versionen implementieren.