postgresql 如何使用30分钟规则获取第1列值并会话活动数据

bwleehnv  于 2023-03-12  发布在  PostgreSQL
关注(0)|答案(1)|浏览(126)

我有一个名为myTable的活动表,数据如下所示:

CREATE TABLE myTable
(
    userid      text,
    webid         text,
    "ts" timestamp
);

INSERT INTO myTable
    ("userid", "webid", "ts")
VALUES ('A', '34', '2023-01-31 16:34:49.000'),
       ('A', '97', '2023-01-31 16:34:58.000'),
       ('A', '17', '2023-01-31 17:35:02.000'),
       ('A', '17', '2023-01-31 17:35:07.000'),
       ('A', '17', '2023-01-31 17:35:18.000'),
       ('A', '1', '2023-01-31 17:35:37.000'),
       ('A', '1', '2023-01-31 17:35:38.000'),
       ('A', '77', '2023-01-31 17:35:41.000'),
       ('A', '77', '2023-01-31 17:35:42.000'),
       ('A', '1', '2023-01-31 17:37:10.000'),
       ('A', '1', '2023-01-31 17:37:12.000'),
       ('A', '77', '2023-01-31 17:37:14.000'),
       ('A', '77', '2023-01-31 17:52:14.000'),
       ('A', '77', '2023-01-31 18:12:14.000'),
       ('A', '77', '2023-01-31 18:45:14.000'),
       ('A', '77', '2023-01-31 18:55:15.000'),
       ('B', '33', '2023-01-31 06:37:15.000'),
       ('B', '56', '2023-01-31 06:40:15.000')
  ;

| 用户识别码|网络标识符|ts|
| - ------|- ------|- ------|
| A类|三十四|2023年1月31日16时34分49秒|
| A类|九十七|2023年1月31日17时34分58秒|
| A类|十七|2023年1月31日17时35分02秒|
| A类|十七|2023年1月31日17时35分07秒|
| A类|十七|2023年1月31日17时35分18秒|
| A类|1个|2023年1月31日17时35分37秒|
| A类|1个|2023年1月31日17时35分38秒|
| A类|七十七|2023年1月31日17时35分41秒|
| A类|七十七|2023年1月31日17时35分42秒|
| A类|1个|2023年1月31日17时37分10秒|
| A类|1个|2023年1月31日17时37分12秒|
| A类|七十七|2023年1月31日17时37分14秒|
| A类|七十七|2023年1月31日17时52分14秒|
| A类|七十七|2023年1月31日18时12分14秒|
| A类|七十七|2023年1月31日18时45分14秒|
| A类|七十七|2023年1月31日18时55分15秒|
| B|三十三|2023年1月31日06:37:15|
| B|五十六|2023年1月31日06时40分15秒|
我要返回的输出如下:
| 用户识别码|网络标识符|ts|第一个_ts|会话标识|
| - ------|- ------|- ------|- ------|- ------|
| A类|三十四|2023年1月31日16时34分49秒|2023年1月31日16时34分49秒|1个|
| A类|九十七|2023年1月31日17时34分58秒|2023年1月31日17时34分58秒|第二章|
| A类|十七|2023年1月31日17时35分02秒|2023年1月31日17时35分02秒|三个|
| A类|十七|2023年1月31日17时35分07秒|2023年1月31日17时35分02秒|三个|
| A类|十七|2023年1月31日17时35分18秒|2023年1月31日17时35分02秒|三个|
| A类|1个|2023年1月31日17时35分37秒|2023年1月31日17时35分37秒|四个|
| A类|1个|2023年1月31日17时35分38秒|2023年1月31日17时35分37秒|四个|
| A类|七十七|2023年1月31日17时35分41秒|2023年1月31日17时35分41秒|五个|
| A类|七十七|2023年1月31日17时35分42秒|2023年1月31日17时35分41秒|五个|
| A类|1个|2023年1月31日17时37分10秒|2023年1月31日17时37分10秒|六个|
| A类|1个|2023年1月31日17时37分12秒|2023年1月31日17时37分10秒|六个|
| A类|七十七|2023年1月31日17时37分14秒|2023年1月31日17时37分14秒|七|
| A类|七十七|2023年1月31日17时40分14秒|2023年1月31日17时52分14秒|七|
| A类|七十七|2023年1月31日18时15分14秒|2023年1月31日18时12分14秒|七|
| A类|七十七|2023年1月31日18时37分15秒|2023年1月31日18时45分14秒|八个|
| A类|七十七|2023年1月31日18时37分15秒|2023年1月31日18时55分15秒|八个|
| B|三十三|2023年1月31日06:37:15|2023年1月31日06:37:15|1个|
| B|五十六|2023年1月31日06时40分15秒|2023年1月31日06时40分15秒|第二章|
first_ts表示第一个时间戳,first_ts的逻辑是如果一个userid在连续的时间戳访问同一个webid,并且每个时间戳的时间间隔福尔斯30分钟以内,则将这些访问记录为一个会话,例如第3、4、5行,每个时间间隔都小于30分钟,则其first_ts为第一个事件的时间戳,即2023-01-31 17:35:02。
如果用户ID访问一个webid,跳转到另一个webid,返回到第一个webid,first_ts将刷新到当前时间戳,例如第6、7、8、9、10、11行,用户ID A先访问webid=1,但跳转到webid=77,返回到webid=1后,第10行和第11行应记录为一个新的session,具有新的first_ts。
如果userid访问webid的时间戳是连续的,并且有一个时间戳的间隔大于30分钟,也会中断会话,同时刷新first_ts,例如第12、13、14、15、16行,第14行和第15行之间的时间间隔为33分钟(〉30),因此对于行15和行16,它们的first_ts被刷新为当前时间戳,并且它们将成为新会话。
我在这里分享我的脚本,脚本没有包括session_id,因为我发现它没有返回正确的first_ts,让我对session_id执行dense_rank。extract(epoch())函数与sql中的datediff()函数相同。因为我使用Postgresql测试数据,它没有datediff()函数。所以我使用exract(epoch())来计算时差。

WITH cte AS (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY userid ORDER BY ts) rn1,
              ROW_NUMBER() OVER (PARTITION BY userid, webid ORDER BY ts) rn2
    FROM myTable
)

SELECT userid, webid, ts,
       lag(ts,1) over(partition by userid order by ts) as previous_ts,
       case when extract(epoch from (ts - lag(ts,1) over(partition by userid order by ts asc))) <= 1800 then MIN(ts) OVER (PARTITION BY userid, webid, rn1 - rn2) 
            when extract(epoch from (ts - lag(ts,1) over(partition by userid order by ts asc))) > 1800 then ts
            when lag(ts,1) over(partition by userid order by ts) is NULL then ts
       end as first_ts
FROM cte
ORDER BY userid, ts;

有人有更好的解决方案来完成这个任务吗?谢谢!
脚本参考:https://dbfiddle.uk/SL7EtQSy

7gyucuyw

7gyucuyw1#

这看起来像是一个间隙和孤岛问题,其中孤岛是属于同一用户和webid的连续行,间隙小于30分钟。
下面是一种方法:

select t.*, min(ts) over(partition by userid, session_id order by ts) first_ts
from (
    select t.*,
        1 + count(*) 
            filter(where webid != lag_webid or ts > lag_ts + interval '30' minute) 
            over(partition by userid order by ts) session_id
    from (
        select t.*, 
            lag(webid, 1, webid) over(partition by userid order by ts) lag_webid,
            lag(ts, 1, ts)       over(partition by userid order by ts) lag_ts
        from mytable t
    ) t
) t

最内部的子查询检索每个用户的“最后一个”webid和时间戳。有了这些信息,我们就可以用有条件的间隔计数(session_id列)来标识每组记录。最后一步是恢复每个岛的第一个时间戳,即first_ts
在**your DB Fiddle**中,这将生成:
| 用户识别码|网络标识符|ts|滞后_网络ID|滞后_ts|会话标识|第一个_ts|
| - ------|- ------|- ------|- ------|- ------|- ------|- ------|
| A类|三十四|2023年1月31日16时34分49秒|三十四|2023年1月31日16时34分49秒|1个|2023年1月31日16时34分49秒|
| A类|九十七|2023年1月31日16时34分58秒|三十四|2023年1月31日16时34分49秒|第二章|2023年1月31日16时34分58秒|
| A类|十七|2023年1月31日17时35分02秒|九十七|2023年1月31日16时34分58秒|三个|2023年1月31日17时35分02秒|
| A类|十七|2023年1月31日17时35分07秒|十七|2023年1月31日17时35分02秒|三个|2023年1月31日17时35分02秒|
| A类|十七|2023年1月31日17时35分18秒|十七|2023年1月31日17时35分07秒|三个|2023年1月31日17时35分02秒|
| A类|1个|2023年1月31日17时35分37秒|十七|2023年1月31日17时35分18秒|四个|2023年1月31日17时35分37秒|
| A类|1个|2023年1月31日17时35分38秒|1个|2023年1月31日17时35分37秒|四个|2023年1月31日17时35分37秒|
| A类|七十七|2023年1月31日17时35分41秒|1个|2023年1月31日17时35分38秒|五个|2023年1月31日17时35分41秒|
| A类|七十七|2023年1月31日17时35分42秒|七十七|2023年1月31日17时35分41秒|五个|2023年1月31日17时35分41秒|
| A类|1个|2023年1月31日17时37分10秒|七十七|2023年1月31日17时35分42秒|六个|2023年1月31日17时37分10秒|
| A类|1个|2023年1月31日17时37分12秒|1个|2023年1月31日17时37分10秒|六个|2023年1月31日17时37分10秒|
| A类|七十七|2023年1月31日17时37分14秒|1个|2023年1月31日17时37分12秒|七|2023年1月31日17时37分14秒|
| A类|七十七|2023年1月31日17时52分14秒|七十七|2023年1月31日17时37分14秒|七|2023年1月31日17时37分14秒|
| A类|七十七|2023年1月31日18时12分14秒|七十七|2023年1月31日17时52分14秒|七|2023年1月31日17时37分14秒|
| A类|七十七|2023年1月31日18时45分14秒|七十七|2023年1月31日18时12分14秒|八个|2023年1月31日18时45分14秒|
| A类|七十七|2023年1月31日18时55分15秒|七十七|2023年1月31日18时45分14秒|八个|2023年1月31日18时45分14秒|
| B|三十三|2023年1月31日06:37:15|三十三|2023年1月31日06:37:15|1个|2023年1月31日06:37:15|
| B|五十六|2023年1月31日06时40分15秒|三十三|2023年1月31日06:37:15|第二章|2023年1月31日06时40分15秒|
旁注:条件计数是Postgres特有的(很少有数据库支持filter子句来聚合/窗口函数)。

count(*) 
    filter(where webid != lag_webid or ts > lag_ts + interval '30' minute) 
    over(partition by userid order by ts) session_id

其中:

sum(case when webid != lag_webid or ts > lag_ts + interval '30' minute then 1 else 0 end) 
    over(partition by userid order by ts) session_id

相关问题