我有这样一张桌子:
Name activity time user1 A1 12:00 user1 E3 12:01 user1 A2 12:02 user2 A1 10:05 user2 A2 10:06 user2 A3 10:07 user2 M6 10:07 user2 B1 10:08 user3 A1 14:15 user3 B2 14:20 user3 D1 14:25 user3 D2 14:30
现在,我需要这样的结果:
Name activity next_activity user1 A2 NULL user2 A3 B1 user3 A1 B2
我想检查每个用户A组的最后一项活动以及接下来B组的活动类型(B组的活动总是在A组活动后进行).其他类型的活动对我来说并不感兴趣.我试过使用该lead()
功能,但它没有奏效.
我怎么能解决我的问题?
测试设置:
CREATE TEMP TABLE t (name text, activity text, time time); INSERT INTO t values ('user1', 'A1', '12:00') ,('user1', 'E3', '12:01') ,('user1', 'A2', '12:02') ,('user2', 'A1', '10:05') ,('user2', 'A2', '10:06') ,('user2', 'A3', '10:07') ,('user2', 'M6', '10:07') ,('user2', 'B1', '10:08') ,('user3', 'A1', '14:15') ,('user3', 'B2', '14:20') ,('user3', 'D1', '14:25') ,('user3', 'D2', '14:30');
你的定义:
B组的活动总是在A组活动后进行.
..逻辑上暗示在一个或多个A活动之后,每个用户有0或1个B活动.按顺序进行的活动不得超过1个.
你可以把它与一个窗口功能的工作,DISTINCT ON
并且CASE
,这应该是最快的方式很少每个用户行(见下文):
SELECT name , CASE WHEN a2 LIKE 'B%' THEN a1 ELSE a2 END AS activity , CASE WHEN a2 LIKE 'B%' THEN a2 END AS next_activity FROM ( SELECT DISTINCT ON (name) name , lead(activity) OVER (PARTITION BY name ORDER BY time DESC) AS a1 , activity AS a2 FROM t WHERE (activity LIKE 'A%' OR activity LIKE 'B%') ORDER BY name, time DESC ) sub;
如果没有添加分支,则SQL CASE
表达式默认为,所以我保持简短.NULL
ELSE
也假定time
是定义的NOT NULL
.否则,您可能想要添加NULLS LAST
.为什么?
选择每个GROUP BY组中的第一行?
(activity LIKE 'A%' OR activity LIKE 'B%')
比activity ~ '^[AB]'
旧版本的Postgres 更加冗长,但通常更快.关于模式匹配:
与PostgreSQL中的LIKE,SIMILAR TO或正则表达式匹配的模式
这实际上是可能的.您可以将aggregate FILTER
子句与OVER
window函数的子句组合在一起.但是:
该FILTER
条款本身只能从当前行值工作.
更重要的是, FILTER
没有实现像纯窗函数lead()
或lag()
在Postgres的9.6(还) -仅适用于聚集函数.
如果你试试:
lead(activity) FILTER (WHERE activity LIKE 'A%') OVER () AS activity
Postgres会告诉你:
FILTER is not implemented for non-aggregate window functions
关于 FILTER
:
如何简化此游戏统计查询?
引用窗口函数的FILTER子句中的当前行
(对于少数用户数每个用户行,几乎任何查询速度快,即使没有索引).
对于许多用户和每个用户几行,上面的第一个查询应该是最快的.查看链接的答案上面关于索引和性能.
对于每个用户的许多行,有(可能更多)更快的技术,具体取决于您的设置的其他详细信息:
优化GROUP BY查询以检索每个用户的最新记录