我有一张这样的表:
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()
函数,但它没有用。
我如何解决我的问题?
答案 0 :(得分:8)
测试设置:
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;
如果没有添加CASE
分支,则SQL NULL
表达式默认为ELSE
,所以我保持简短。
还假设time
定义为NOT NULL
。否则,您可能想要添加NULLS LAST
。为什么呢?
(activity LIKE 'A%' OR activity LIKE 'B%')
比activity ~ '^[AB]'
更详细,但在较旧版本的Postgres中通常更快。关于模式匹配:
实际上可能。您可以将聚合FILTER
子句与窗口函数的OVER
子句组合在一起。的然而强>:
FILTER
子句本身只能用于当前行的值。
更重要的是,FILTER
并未针对Postgres 9.6中的lead()
或lag()
等纯窗口函数实现 - 仅适用于aggregate functions。
如果您尝试:
lead(activity) FILTER (WHERE activity LIKE 'A%') OVER () AS activity
Postgres会告诉你:
FILTER is not implemented for non-aggregate window functions
关于FILTER
:
(对于 少数 用户,每个用户 少数 行,几乎任何即使没有索引,查询也很快。)
对于 许多 用户以及每个用户的 少数 行,上面的第一个查询应该是最快的。请参阅上面有关索引和效果的linked answer。
对于每个用户的 许多 行,有(可能很多)更快的技术,具体取决于您的设置的其他详细信息:
答案 1 :(得分:0)
select distinct on(name) name,activity,next_activity
from (select name,activity,time
,lead(activity) over (partition by name order by time) as next_activity
from t
where left(activity,1) in ('A','B')
) t
where left(activity,1) = 'A'
order by name,time desc