过去X个月内PostgreSQL累积计数

时间:2013-03-26 07:30:07

标签: postgresql aggregate-functions window-functions postgresql-performance

鉴于下表:

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

1 个答案:

答案 0 :(得分:3)

正确的方法是使用RANGE (INTERVAL '6' MONTH) PRECEDING上的窗口但不幸的是PostgreSQL不支持有界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的{​​{1}}窗口尝试执行此操作更为可取。您必须将整个日期范围的sum与所有不同uid的集合交叉加入,然后将外部联接与generate_series表格对齐,并使用cnts处理窗口,然后筛选出具有空计数的行。毋庸置疑,这是一种比简单的自我加入更为折磨的做事方式。


对于您的示例数据,以下查询将产生您在上面显示的相同结果:

sum

然而,这是完全错误的。我正在展示它主要是为了说明样本数据不足以进行可靠的测试,因为结果基本上与纯粹的运气相匹配。您的样品在六个月内都没有超过两个样品。样本数据很棒,但您需要考虑角落情况,就像您编写单元测试一样。你应该让uid在相同的日期没有开始和停止,具有不同的计数等。