使用窗口函数确定 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
这会为 total
和 same
生成正确的值,但不考虑 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_id
user_id
和date_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 窗口函数的干净方法?