SQL 查询时间序列数据以查找用户 activity 的趋势计数

SQL query time series data to find trend counts for user activity

使用 Postgres 8.4。我有一个 table 的用户 activity,看起来有点像这样:

userid    |  timestamp           |  action
---------------------------------------------
0001      |  11/11/2015 9:00:02  |  X
0001      |  11/11/2015 9:00:22  |  Y
0002      |  11/11/2015 9:01:02  |  Z
0002      |  11/11/2015 9:03:02  |  W 
0003      |  11/11/2015 9:04:02  |  X
0004      |  11/11/2015 9:05:02  |  Y

我需要做的是计算执行一系列操作的用户数量 X,然后是 YX 然后 Y 然后 Z 统计有多少用户进入下一个步骤.

所以我输入了一组有序的操作,我想计算有多少用户完成了这些操作(第一步:操作 1,第二步:操作 2,第三步)

我正在努力获得类似

的结果
step | action |  count
=======================
 1    |  X     | 100       <---- 100 users did X
 2    |  Y     |  55       <-----55 did X and then Y (45 dropped away)
 3    |  Z     |  12       <-----12 did X and then Y and then Z (43 more dropped)

如您所见,计数一直在减少:100 名用户做了 X,其中 55 名用户做了 Y,12 名用户做了 Z。

我怎样才能做到这一点?

我相信一定有更好的方法来使用其他 SQL 功能。

但一个简单的方法就是执行类似于我在下面粘贴的查询。

那会为您提供执行 X-Y-Z 的用户,X-Y 应该可以通过修改此查询轻松完成。

select count(distinct(userid)) from user_activity u1
where action = 'Z' 
and exists
  (select userid from user_activity u2
   where u2.userid = u1.userid
   and u2.date < u1.date
   and u2.action = 'Y'
   and exists (
     select userid from user_activity u3
     where u3.userid = u2.userid
     and u3.date < u2.date
     and u3.action = 'X'
     )
   )

这是一种相当蛮力的方法。使用 listagg() 创建序列,然后查找它们:

select p.pattern, count(t.actions)
from (select 'X' as pattern union all select 'XY' union all SELECT 'XYZ'
     ) p left join
     (select userid, listagg(action, '') within group (order by timestamp) actions
      from table t
      group by userid
     ) t 
     on t.actions like concat('%', p.pattern, '%')
group by p.pattern;

最简单的解决方案可能是使用 LEFT JOIN 将 table 与自身连接起来:

WITH actions(action) AS(
  VALUES ('X'),('Y'),('Z'))
SELECT d.action
       ,Count(DISTINCT a.userid)
FROM table1 as a
  LEFT JOIN table1 AS b
    ON a.userid = b.userid AND b.action = 'Y' AND a.timestamp < b.timestamp
  LEFT JOIN table1 AS c
    ON a.userid = c.userid AND c.action = 'Z' AND b.timestamp < c.timestamp
  JOIN actions AS d
    ON d.action IN (a.action, b.action, c.action)
WHERE a.action = 'X'
GROUP BY d.action

fiddle