特別な難しさ このタスクの概要:時間範囲内のデータポイントを選択するだけでなく、最新を考慮する必要があります。 前のデータポイント 時間範囲と最も早い 後のデータポイント さらに時間範囲。これは行ごとに異なり、各データポイントは存在する場合と存在しない場合があります。高度なクエリが必要であり、インデックスの使用が困難になります。
範囲タイプを使用できます
および
WITH input(a,b) AS (SELECT '2013-01-01'::date -- your time frame here
, '2013-01-15'::date) -- inclusive borders
SELECT store_id, product_id
, sum(upper(days) - lower(days)) AS days_in_range
, round(sum(value * (upper(days) - lower(days)))::numeric
/ (SELECT b-a+1 FROM input), 2) AS your_result
, round(sum(value * (upper(days) - lower(days)))::numeric
/ sum(upper(days) - lower(days)), 2) AS my_result
FROM (
SELECT store_id, product_id, value, s.day_range * x.day_range AS days
FROM (
SELECT store_id, product_id, value
, daterange (day, lead(day, 1, now()::date)
OVER (PARTITION BY store_id, product_id ORDER BY day)) AS day_range
FROM stock
) s
JOIN (
SELECT daterange(a, b+1) AS day_range
FROM input
) x ON s.day_range && x.day_range
) sub
GROUP BY 1,2
ORDER BY 1,2;
列名day
を使用していることに注意してください date
の代わりに 。基本的な型名を列名として使用することはありません。
サブクエリでsub
ウィンドウ関数lead()
を使用して、各アイテムの次の行から日を取得します 、組み込みオプションを使用して、次の行がないデフォルトとして「今日」を提供します。
これを使用して、daterange
を作成します。 重複演算子&&
を使用して入力と照合します 、交差演算子*
を使用して結果の日付範囲を計算します 。
ここにあるすべての範囲は排他的です 上部の境界線。そのため、入力範囲に1日を追加します。このようにして、lower(range)
を単純に減算できます。 upper(range)
から 日数を取得します。
「昨日」は信頼できるデータのある最新の日だと思います。 「今日」は、実際のアプリケーションでは変更される可能性があります。したがって、私は「今日」(now()::date
)オープンレンジの排他的な上限として。
2つの結果を提供します:
-
your_result
表示された結果に同意します。
無条件に日付範囲の日数で除算します。たとえば、アイテムが最終日のみリストされている場合、「平均」は非常に低くなります(誤解を招く可能性があります!)。 -
my_result
同じかそれ以上の数を計算します。
私は実際ので割ります アイテムがリストされている日数。たとえば、アイテムが最終日のみリストされている場合、リストされている値を平均として返します。
違いを理解するために、アイテムがリストされた日数を追加しました:days_in_range
インデックスとパフォーマンス
この種のデータの場合、通常、古い行は変更されません。これは、マテリアライズドビューの優れたケースになります。 :
CREATE MATERIALIZED VIEW mv_stock AS
SELECT store_id, product_id, value
, daterange (day, lead(day, 1, now()::date) OVER (PARTITION BY store_id, product_id
ORDER BY day)) AS day_range
FROM stock;
次に、関連する演算子をサポートするGiSTインデックスを追加できます&&
:
CREATE INDEX mv_stock_range_idx ON mv_stock USING gist (day_range);
大きなテストケース
20万行でより現実的なテストを実行しました。 MVを使用したクエリは約6倍の速さで、@Joopのクエリの約10倍の速さでした。パフォーマンスはデータ分散に大きく依存します。 MVは、大きなテーブルと高頻度のエントリで最も役立ちます。また、テーブルにこのクエリに関係のない列がある場合、MVを小さくすることができます。コストと利益の問題。
私はこれまでに投稿された(そして適応された)すべてのソリューションを大きなフィドルに入れて遊んでいます:
大きなテストケースを備えたSQLフィドル。
4万行しかないSQLフィドル
-sqlfiddle.comのタイムアウトを回避するため