それは非常に多くの依存状況と正確な要件に。質問への私のコメントを検討してください。
シンプルなソリューション
DISTINCT ON
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;
順序付けられた結果。
またはNOT EXISTS
、標準SQLで(私が知っているすべての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
);
同じ結果ですが、任意のソート順で-を追加しない限りORDER BY
。
データの分布、正確な要件およびインデックスに応じて、これらのいずれかが高速になる場合があります。
一般にDISTINCT ON
、勝者はその上にソートされた結果を取得します。しかし、場合によっては、他のクエリ手法は(はるかに)高速です。下記参照。
最大/最小値を計算するためのサブクエリを使用するソリューションは、一般に低速です。CTEを使用したバリアントは一般に低速です。
(別の回答で提案されたような)単純なビューは、Postgresでのパフォーマンスにはまったく役立ちません。
SQLフィドル。
適切なソリューション
文字列と照合
まず、テーブルのレイアウトが最適ではないことに悩まされます。些細なことのように思えるかもしれませんが、スキーマを正規化することは大いに役立ちます。
ソート文字タイプ(text
、varchar
、...) -ロケールに応じて行う必要がありますCOLLATION特にインチ ほとんどの場合、DBはローカルのルールセットを使用します(私の場合:などde_AT.UTF-8
)。で調べる:
SHOW lc_collate;
これにより、ソートとインデックスの検索が遅くなります。文字列(商品の名前)が長いほど悪化します。出力の照合ルール(またはソート順)を実際に気にしない場合は、以下を追加するとより高速になります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;
照合を2つの場所に追加したことに注意してください。
私のテストでは、それぞれ2万行と非常に基本的な名前(「good123」)で2倍の速さです。
インデックス
クエリがインデックスを使用することになっている場合、文字データを含む列は一致する照合を使用する必要があります(good
例)。
CREATE INDEX inventory_good_date_desc_collate_c_idx
ON price(good COLLATE "C", the_date DESC);
SOに関するこの関連する回答の最後の2つの章を必ずお読みください。
同じ列に異なる照合を持つ複数のインデックスを持つこともできます-他のクエリの別の(またはデフォルトの)照合に従ってソートされた商品も必要な場合。
ノーマライズ
冗長な文字列(良い名前)は、テーブルとインデックスを膨張させ、すべてをさらに遅くします。適切なテーブルレイアウトを使用すると、最初の問題のほとんどを回避できます。次のようになります。
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));
主キーは、必要なすべてのインデックスを(ほぼ)自動的に提供します。
欠落している詳細に応じて、2列目の降順で複数列インデックスをprice
使用すると、パフォーマンスが向上する場合があります。
CREATE INDEX price_good_date_desc_idx ON price(good, the_date DESC);
この場合も、照合はクエリと一致する必要があります(上記を参照)。
Postgres 9.2以降では、特にテーブルに追加の列があり、テーブルがカバーインデックスよりも大幅に大きくなる場合、インデックスオンリースキャンの「インデックスのカバー」がさらに役立ちます。
これらの結果のクエリははるかに高速です。
存在しない
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
);
区別する
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フィドル。
より高速なソリューション
それでも十分に高速でない場合は、より高速なソリューションがある可能性があります。
再帰CTE / JOIN LATERAL
/相関サブクエリ
特に、商品あたりの価格が多いデータ分布の場合:
マテリアライズドビュー
これを頻繁かつ高速に実行する必要がある場合は、マテリアライズドビューを作成することをお勧めします。過去の日付の価格と在庫はめったに変わらないと仮定するのは安全だと思います。結果を一度計算し、スナップショットをマテリアライズドビューとして保存します。
Postgres 9.3+は、マテリアライズドビューの自動サポートを備えています。古いバージョンの基本バージョンを簡単に実装できます。