ローリングサム/カウント/日付間隔の平均


20

18か月にわたる1,000のエンティティにまたがるトランザクションのデータベースで、クエリを実行して、可能な30日間ごとにentity_idトランザクション量のSUMとその30日間のトランザクションのCOUNTでグループ化します。クエリを実行できる方法でデータを返します。多くのテストの後、このコードは私が望むものの多くを達成します:

SELECT id, trans_ref_no, amount, trans_date, entity_id,
    SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
    COUNT(id)   OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
  FROM transactiondb;

そして、次のような構造の大きなクエリで使用します。

SELECT * FROM (
  SELECT id, trans_ref_no, amount, trans_date, entity_id,
      SUM(amount) OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_total,
      COUNT(id)   OVER(PARTITION BY entity_id, date_trunc('month',trans_date) ORDER BY entity_id, trans_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS trans_count
    FROM transactiondb ) q
WHERE trans_count >= 4
AND trans_total >= 50000;

このクエリがカバーしないケースは、トランザクションカウントが複数の月にまたがる場合でも、互いに30日以内である場合です。このタイプのクエリはPostgresで可能ですか?もしそうなら、私はすべての入力を歓迎します。他のトピックの多くでは、ローリングではなく「実行」アグリゲートについて説明しています。

更新

CREATE TABLEスクリプト:

CREATE TABLE transactiondb (
    id integer NOT NULL,
    trans_ref_no character varying(255),
    amount numeric(18,2),
    trans_date date,
    entity_id integer
);

サンプルデータはこちらにあります。PostgreSQL 9.1.16を実行しています。

理想的な出力が含まれるであろうSUM(amount)COUNT()、ローリング30日間にわたるすべてのトランザクションの。たとえば、次の画像を参照してください。

理想的には「セット」に含まれる行の例ですが、私のセットが月ごとに静的であるためではありません。

緑の日付の強調表示は、クエリに含まれるものを示します。黄色の行の強調表示は、セットの一部になりたいレコードを示します。

前の読書:


1
every possible 30-day period by entity_id期間はいつでも開始できるということですが、(うるう年ではない)1年で365の期間が可能ですか?または、実際の取引のある日のみを個別に期間の開始と見なしますentity_id か?どちらにしても、テーブル定義、Postgresバージョン、サンプルデータ、およびサンプルの期待される結果を提供してください。
アーウィンブランドステッター

理論的には、私はいつでも意味していましたが、実際には、取引がない日を考慮する必要はありません。サンプルデータとテーブル定義を投稿しました。
tufelkinder

そのため、実際の各トランザクションから始まるentity_id 30日間に同じ行を蓄積します。同じトランザクションに複数のトランザクションが存在するか、その組み合わせが一意に定義されていますか?テーブル定義に制約がないかPKがありますが、制約が欠落しているようです...(trans_date, entity_id)UNIQUE
Erwin Brandstetter

唯一の制約はid主キーです。1日にエンティティごとに複数のトランザクションが存在する場合があります。
tufelkinder

データ配布について:ほとんどの日(entity_idごと)のエントリはありますか?
アーウィンブランドステッター

回答:


26

あなたが持っているクエリ

WINDOW句を使用してクエリを単純化することもできますが、それは単にクエリプランを変更するのではなく、構文を短縮するだけです。

SELECT id, trans_ref_no, amount, trans_date, entity_id
     , SUM(amount) OVER w AS trans_total
     , COUNT(*)    OVER w AS trans_count
FROM   transactiondb
WINDOW w AS (PARTITION BY entity_id, date_trunc('month',trans_date)
             ORDER BY trans_date
             ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING);
  • 確かに定義されているcount(*)ので、少し速くも使用しますか?idNOT NULL
  • そしてORDER BY entity_id、あなたはすでにあなたがいるので必要はありませんPARTITION BY entity_id

ただし、さらに単純化することもできます。ウィンドウ定義に
追加ORDER BYしないでください。クエリには関係ありません。次に、カスタムウィンドウフレームを定義する必要もありません。

SELECT id, trans_ref_no, amount, trans_date, entity_id
     , SUM(amount) OVER w AS trans_total
     , COUNT(*)    OVER w AS trans_count
FROM   transactiondb
WINDOW w AS (PARTITION BY entity_id, date_trunc('month',trans_date);

シンプルで、高速です静的な月を使用し、現在のバージョンよりも優れています。

あなたが望むかもしれないクエリ

...は明確に定義されていないため、これらの前提に基づいて構築します。

anyの最初と最後のトランザクション内の30日間ごとのトランザクションと金額をカウントしますentity_id。アクティビティのない先行期間と後続期間を除外しますが、それらの外側の境界内のすべての可能な30日間の期間を含めます。

SELECT entity_id, trans_date
     , COALESCE(sum(daily_amount) OVER w, 0) AS trans_total
     , COALESCE(sum(daily_count)  OVER w, 0) AS trans_count
FROM  (
   SELECT entity_id
        , generate_series (min(trans_date)::timestamp
                         , GREATEST(min(trans_date), max(trans_date) - 29)::timestamp
                         , interval '1 day')::date AS trans_date
   FROM   transactiondb 
   GROUP  BY 1
   ) x
LEFT JOIN (
   SELECT entity_id, trans_date
        , sum(amount) AS daily_amount, count(*) AS daily_count
   FROM   transactiondb
   GROUP  BY 1, 2
   ) t USING (entity_id, trans_date)
WINDOW w AS (PARTITION BY entity_id ORDER BY trans_date
             ROWS BETWEEN CURRENT ROW AND 29 FOLLOWING);

これにはentity_id、集計とtrans_dateの最初の日(を含む)で、。個々の行の値を取得して、ベーステーブルにもう一度結合するには...

基本的な難易度は、ここで説明したものと同じです。

ウィンドウのフレーム定義は、現在の行の値に依存できません。

むしろ呼び出しgenerate_series()timestamp入力:

実際に必要なクエリ

質問の更新とディスカッションの後:各実際のトランザクションで始まる30日間のウィンドウに
同じ行を蓄積しますentity_id

データはまばらに分散されているため、Postgres 9.1には結合がないため、範囲条件を使用して自己結合を実行する方が効率的LATERALです。

SELECT t0.id, t0.amount, t0.trans_date, t0.entity_id
     , sum(t1.amount) AS trans_total, count(*) AS trans_count
FROM   transactiondb t0
JOIN   transactiondb t1 USING (entity_id)
WHERE  t1.trans_date >= t0.trans_date
AND    t1.trans_date <  t0.trans_date + 30  -- exclude upper bound
-- AND    t0.entity_id = 114284  -- or pick a single entity ...
GROUP  BY t0.id  -- is PK!
ORDER  BY t0.trans_date, t0.id

SQLフィドル。

ローリングウィンドウは、(パフォーマンスに関して)ほとんどの日のデータでのみ意味があります。

これは1日あたりの重複を集計しませ(trans_date, entity_id)が、同じ日のすべての行は常に30日間のウィンドウに含まれます。

大きなテーブルの場合、このようなカバリングインデックスはかなり役立ちます。

CREATE INDEX transactiondb_foo_idx
ON transactiondb (entity_id, trans_date, amount);

最後の列amountは、そこからインデックスのみのスキャンを取得する場合にのみ役立ちます。それ以外の場合はドロップします。

ただし、とにかくテーブル全体を選択している間は使用されません。小さなサブセットのクエリをサポートします。


...今のデータでそれをテストして、クエリが実際にやっているすべてを理解しようとすると、このルックス本当に良い
tufelkinder

@tufelkinder:更新された質問の解決策を追加しました。
アーウィンブランドステッター

今それを確認します。私は私のtransactiondb上で直接それを実行しようとすると、私はとアウトそれはエラー、...それはSQLフィドルに実行されることを興味をそそられていますcolumn "t0.amount" must appear in the GROUP BY clause...
tufelkinder

@tufelkinder:テストケースを100行に減らしました。sqlfiddleは、テストデータのサイズを制限します。Jake(著者)は、数か月前に制限の制限を緩和し、サイトが簡単に停止することを防ぎました。
アーウィンブランドステッター

1
完全なデータベースでテストするために必要な遅延で申し訳ありません。いつものように、あなたの答えは非常に詳細で教育的でした。ありがとうございました!
tufelkinder
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.