使用窗口函数确定 PostgreSQL 中的 30 天运行计数

Posted

技术标签:

【中文标题】使用窗口函数确定 PostgreSQL 中的 30 天运行计数【英文标题】:Using window function to determine 30-day running counts in PostgreSQL 【发布时间】:2018-02-13 00:05:42 【问题描述】:

我有下表(称为table1):

row_id  session_id    date_end    user_id  item_id 
---------------------------------------------------
3962     5958255      2017-11-07  3249480      1
4553     5959689      2017-11-07  3249484      1
4554     5959689      2017-11-07  3249484      1
8775     5968439      2017-11-08  3249492      4
6706     5965190      2017-11-08  3249492      2
6779     5965280      2017-11-08  3249492      3
6778     5965280      2017-11-08  3249492      3
8774     5968439      2017-11-08  3249492      4
6685     5965159      2017-11-08  3249502      1
5314     5962257      2017-11-07  3249504      1
5315     5962257      2017-11-07  3249504      1
13564    5982665      2017-11-09  3249510      1
13565    5982665      2017-11-09  3249510      1
238      5941818      2017-11-06  3249540      1
8078     5967039      2017-11-08  3249540      3
13981    5984747      2017-11-09  3249540      4
127080   6267047      2017-11-30  3249540      10

查询此数据库时,我需要 3 个新列:

每个用户购买的商品数量 与当前行包含相同 item_id 的已购买商品的数量 与当前行中包含不同 item_id 的商品数量

但是,我需要针对 30 天的时间段进行所有这些计数。例如,user_id 3249492 的行应为:

row_id  session_id    date_end   user_id   item_id   total   same   diff
8775     5968439      2017-11-08  3249492      4       5       1       3
6706     5965190      2017-11-08  3249492      2       4       0       3
6779     5965280      2017-11-08  3249492      3       3       1       1
6778     5965280      2017-11-08  3249492      3       2       0       1
8774     5968439      2017-11-08  3249492      4       1       0       0

我有以下几点:

SELECT row_id, session_id, date_end, user_id, item_id,
   COUNT(item_id) OVER (PARTITION BY user_id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) as total,
   COUNT(item_id) OVER (PARTITION BY user_id, item_id ORDER BY item_id ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) as same
FROM table1

这会为 totalsame 生成正确的值,但不考虑 30 天窗口。另外,我不知道从哪里开始 diff 列。

SQL 小提琴:http://sqlfiddle.com/#!17/ac833/2

这个 PostgreSQL 9.6

任何帮助将不胜感激。

【问题讨论】:

你能提供一个输入和输出的例子吗? SQL 格式的输入将与rextester.com/DJXQ37562 一样。当前输入没有超过 30 天窗口的数据。 @emilioplatzer 我用 SQL Fiddle 的链接更新了问题。 我不知道怎么做。我想这是不可能的。 【参考方案1】:

30 天运行计数

我们可以使用自联接来获取 30 天的运行计数,而不是使用窗口函数。

WITH thirty_days_window AS (
   SELECT table1.row_id, table1.item_id, "window".item_id AS other_item_id
   FROM table1 join table1 AS "window" ON "window".user_id = table1.user_id AND
      "window".date_end BETWEEN table1.date_end - interval '30 days' AND table1.date_end AND
      "window".row_id <= table1.row_id
),
counts AS (
   SELECT row_id,
          COUNT(*) AS total,
          COUNT(CASE WHEN item_id = other_item_id THEN 1 END) - 1 AS same,
          COUNT(CASE WHEN item_id != other_item_id THEN 1 END) AS diff
   FROM thirty_days_window GROUP BY row_id)
SELECT table1.row_id, session_id, date_end, user_id, table1.item_id,
       total, same, diff
FROM table1 JOIN counts ON counts.row_id = table1.row_id
ORDER BY row_id;

第一部分thirty_days_window 通过在 30 天的窗口中将每一行与具有相同 user_id 的所有行连接起来来创建窗口。我们还假设我们只想要 row_id 低于当前的行。

接下来我们计算行数。 same 只计算item_id 与连接行的item_id 相同的行(减去1 以删除原始行),diff 完全相反,获取所有item_id 不同的行从连接的行开始。

最后我们加入到原来的表中添加session_iduser_iddate_end

使用小提琴中数据的最终结果:

 row_id | session_id |  date_end  | user_id | item_id | total | same | diff 
--------+------------+------------+---------+---------+-------+------+------
   6706 |    5965190 | 2017-11-08 | 3249492 |     151 |     1 |    0 |    0
   6778 |    5965280 | 2017-11-08 | 3249492 |     151 |     2 |    1 |    0
   6779 |    5965280 | 2017-11-08 | 3249492 |     158 |     3 |    0 |    2
   8774 |    5968439 | 2017-11-08 | 3249492 |     151 |     4 |    2 |    1
   8775 |    5968439 | 2017-11-08 | 3249492 |     158 |     5 |    1 |    3
  47046 |    6063745 | 2017-11-15 | 3263305 |     157 |     1 |    0 |    0
  47047 |    6063745 | 2017-11-15 | 3263305 |     158 |     2 |    0 |    1
  59887 |    6094293 | 2017-11-16 | 3263305 |     157 |     3 |    1 |    1
  59888 |    6094294 | 2017-11-16 | 3263305 |     157 |     4 |    2 |    1
  60343 |    6095456 | 2017-11-16 | 3263305 |     157 |     5 |    3 |    1
  60344 |    6095457 | 2017-11-16 | 3263305 |     157 |     6 |    4 |    1
  69112 |    6116357 | 2017-11-17 | 3263305 |     157 |     7 |    5 |    1
  71085 |    6119700 | 2017-11-18 | 3263305 |     157 |     8 |    6 |    1
  71508 |    6120421 | 2017-11-18 | 3250078 |     157 |     1 |    0 |    0
  71509 |    6120421 | 2017-11-18 | 3250078 |     152 |     2 |    0 |    1
  71510 |    6120421 | 2017-11-18 | 3250078 |     156 |     3 |    0 |    2
  71511 |    6120421 | 2017-11-18 | 3250078 |     154 |     4 |    0 |    3
  71512 |    6120421 | 2017-11-18 | 3250078 |     151 |     5 |    0 |    4
  71513 |    6120421 | 2017-11-18 | 3250078 |     158 |     6 |    0 |    5
  72242 |    6121399 | 2017-11-18 | 3263305 |     157 |     9 |    7 |    1
  75696 |    6126280 | 2017-11-19 | 3263305 |     157 |    10 |    8 |    1
  76082 |    6126777 | 2017-11-19 | 3263305 |     157 |    11 |    9 |    1
  77546 |    6129039 | 2017-11-19 | 3263305 |     157 |    12 |   10 |    1
  83754 |    6143858 | 2017-11-20 | 3263305 |     157 |    13 |   11 |    1
  91331 |    6167552 | 2017-11-22 | 3263305 |     157 |    14 |   12 |    1
  92431 |    6171560 | 2017-11-22 | 3263305 |     157 |    15 |   13 |    1
  95073 |    6177870 | 2017-11-23 | 3263305 |     157 |    16 |   14 |    1
  95302 |    6178780 | 2017-11-23 | 3263305 |     157 |    17 |   15 |    1
 287471 |    7164221 | 2018-02-10 | 4516965 |     154 |     1 |    0 |    0
 288750 |    7170955 | 2018-02-11 | 4516965 |     158 |     2 |    0 |    1
 288751 |    7170955 | 2018-02-11 | 4516965 |     151 |     3 |    0 |    2
(31 rows)

编辑

想了想,可以一键查询:

SELECT table1.row_id, MIN(table1.session_id),
   MIN(table1.date_end), MIN(table1.user_id), MIN(table1.item_id),
   COUNT(*) as total,
   COUNT(CASE WHEN table1.item_id = windw.item_id THEN 1 END) - 1 AS same,
   COUNT(CASE WHEN table1.item_id != windw.item_id THEN 1 END)
FROM table1 JOIN table1 AS windw ON windw.user_id = table1.user_id AND
   windw.date_end BETWEEN table1.date_end - INTERVAL '30 days' AND table1.date_end AND
   windw.row_id <= table1.row_id
GROUP BY table1.row_id ORDER BY table1.row_id;

【讨论】:

以上是关于使用窗口函数确定 PostgreSQL 中的 30 天运行计数的主要内容,如果未能解决你的问题,请参考以下文章

在 django ORM 中使用 postgresql 窗口函数的干净方法?

PostgreSQL 分析函数窗口化以查找列中的下一个值

PostgreSQL 中的窗口函数尾随日期

在窗口函数 postgresql 中选择条件

PostgreSQL 中的 PERCENTILE_DISC() 作为窗口函数

检查数组是不是包含在 PostgreSQL 中的另一个数组中