我有一个名为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
1条答案
按热度按时间7gyucuyw1#
这看起来像是一个间隙和孤岛问题,其中孤岛是属于同一用户和webid的连续行,间隙小于30分钟。
下面是一种方法:
最内部的子查询检索每个用户的“最后一个”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
子句来聚合/窗口函数)。其中: