sql >> データベース >  >> RDS >> PostgreSQL

平均在庫履歴表

    特別な難しさ このタスクの概要:時間範囲内のデータポイントを選択するだけでなく、最新を考慮する必要があります。 のデータポイント 時間範囲と最も早い のデータポイント さらに時間範囲。これは行ごとに異なり、各データポイントは存在する場合と存在しない場合があります。高度なクエリが必要であり、インデックスの使用が困難になります。

    範囲タイプを使用できます および演算子 (Postgres 9.2+ )計算を簡素化するために:

    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

    SQLフィドル

    インデックスとパフォーマンス

    この種のデータの場合、通常、古い行は変更されません。これは、マテリアライズドビューの優れたケースになります。 :

    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のタイムアウトを回避するため



    1. Eloquentを使用してLaravelでAUTO_INCREMENTを設定するにはどうすればよいですか?

    2. mysqlのインデックスが多すぎますか?

    3. Oracleデータベースの文字列列の一定時間インデックス

    4. 未定義の変数:pdo、nullでメンバー関数prepare()を呼び出す