2

次の表があるとします。

CREATE TABLE cnts(
  user_id INT,
  month_d DATE,
  cnt INT
)

各(user_id、month_d)ペアの過去6か月の累積カウントを照会したいと思います。私は次のJOINでそれを行うことができます:

SELECT
  S1.month_d AS "month_d",
  S1.user_id AS "user_id",
  SUM(S2.cnt) AS "last_6_months_cnt"
FROM cnts S1
LEFT JOIN cnts S2 ON S1.user_id = S2.user_id
                 AND (S2.month_d BETWEEN (S1.month_d - INTERVAL '5 MONTH') AND S1.month_d)
GROUP BY 1, 2
ORDER BY 2, 1;

しかし、これはウィンドウ関数で解決できるのだろうか?

サンプルデータ:

INSERT INTO cnts(user_id, month_d, cnt) VALUES 
(1, '2013-01-01', 2),
(1, '2013-04-01', 2),
(1, '2013-07-01', 2),
(1, '2013-10-01', 2),

(2, '2013-01-01', 2),
(2, '2013-04-01', 2),
(2, '2013-07-01', 2),
(2, '2013-10-01', 2)
;

期待される結果(上記の結合から):

  month_d   | user_id | last_6_months_cnt 
------------+---------+-------------------
 2013-01-01 |       1 |                 2
 2013-04-01 |       1 |                 4
 2013-07-01 |       1 |                 4
 2013-10-01 |       1 |                 4
 2013-01-01 |       2 |                 2
 2013-04-01 |       2 |                 4
 2013-07-01 |       2 |                 4
 2013-10-01 |       2 |                 4
4

1 に答える 1

4

PostgreSQL12以降

更新RANGE:PostgreSQL12以降はWindowsをサポートするようになりました。

正しい方法は、次のウィンドウを使用することRANGE (INTERVAL '6' MONTH) PRECEDINGです。

demo=> SELECT month_d, user_id, 
              SUM(cnt) OVER (PARTITION BY user_id ORDER BY month_d RANGE INTERVAL '6' MONTH PRECEDING)
       FROM cnts ORDER BY 2,1;

  month_d   | user_id | sum 
------------+---------+-----
 2013-01-01 |       1 |   2
 2013-04-01 |       1 |   4
 2013-07-01 |       1 |   6
 2013-10-01 |       1 |   6
 2013-01-01 |       2 |   2
 2013-04-01 |       2 |   4
 2013-07-01 |       2 |   6
 2013-10-01 |       2 |   6
(8 rows)

PostgreSQL11以前

PostgreSQL 11以前のRANGEウィンドウはまだサポートされていないため、クエリは失敗します。

regress=> SELECT month_d, user_id, 
          SUM(cnt) OVER (PARTITION BY user_id ORDER BY month_d RANGE INTERVAL '6' MONTH PRECEDING) 
          FROM cnts ORDER BY 2,1;
ERROR:  RANGE PRECEDING is only supported with UNBOUNDED
LINE 1: ...(cnt) OVER (PARTITION BY user_id ORDER BY month_d RANGE INTE...

それがないと、参加して戻ってきて、generate_series複数のユーザーIDでそれを行うのは面倒です。ROWSに基づいたウィンドウでこれを実行しようとするよりも、自己結合アプローチの方がはるかに望ましいと思いますsumgenerate_series日付範囲全体のをすべての個別のuidのセットと相互結合し、それをcntsテーブルに対して左外部結合sumし、ウィンドウを介して処理し、nullカウントの行をフィルターで除外する必要があります。言うまでもなく、これは単純な自己参加よりも、物事を行うためのより苦痛な方法です。


サンプルデータの場合、次のクエリは上記と同じ結果を生成します。

-- This query is totally wrong and only works because of overly simple sample data
SELECT 
  month_d, user_id, 
  SUM(cnt) OVER (PARTITION BY user_id ORDER BY month_d ROWS 1 PRECEDING)
FROM cnts
ORDER BY 2,1;

しかし、それは完全に間違っています。結果は基本的に運が良ければ一致するため、サンプルデータが確実なテストには不十分であることを主に説明するために示しています。6か月の範囲内に2つを超えるサンプルが含まれているサンプルはありません。サンプルデータは素晴らしいですが、単体テストを作成するときと同じように、コーナーケースについて考える必要があります。カウントが異なるなど、同じ日付で開始および停止しないuidが必要です。

于 2013-03-26T08:28:50.670 に答える