按累积时间间隔将行分组

Posted

技术标签:

【中文标题】按累积时间间隔将行分组【英文标题】:Partitioning rows into groups by accumulative time interval 【发布时间】:2020-09-16 22:16:13 【问题描述】:

我的搜索会话日志如下所示:

+----------+-------------------------+----------+
|    dt    |       search_time       | searches |
+----------+-------------------------+----------+
| 20200601 | 2020-06-01 00:36:38.000 |        1 |
| 20200601 | 2020-06-01 00:37:38.000 |        1 |
| 20200601 | 2020-06-01 00:39:18.000 |        1 |
| 20200601 | 2020-06-01 01:16:18.000 |        1 |
| 20200601 | 2020-06-01 03:56:38.000 |        1 |
| 20200601 | 2020-06-01 05:36:38.000 |        1 |
| 20200601 | 2020-06-01 05:37:38.000 |        1 |
| 20200601 | 2020-06-01 05:39:38.000 |        1 |
| 20200601 | 2020-06-01 05:41:38.000 |        1 |
| 20200601 | 2020-06-01 07:26:38.000 |        1 |
+----------+-------------------------+----------+

我的任务是将每一行划分为会话组。会话组最多五分钟。

例如:

前 3 个会话将形成一个组会话 1 - 如果我们累积每行之间的分钟数,我们将得到 3 分钟,而第 4 个会话将累积超过 5 分钟,因此它将是一个不同的会话组。

+----------+-------------------------+----------+---------------+
|    dt    |       search_time       | searches | group_session |
+----------+-------------------------+----------+---------------+
| 20200601 | 2020-06-01 00:36:38.000 |        1 |             1 |
| 20200601 | 2020-06-01 00:37:38.000 |        1 |             1 |
| 20200601 | 2020-06-01 00:39:18.000 |        1 |             1 |
| 20200601 | 2020-06-01 01:16:18.000 |        1 |             2 |
+----------+-------------------------+----------+---------------+

我像这样操作表以便为分区做好准备:

WITH [Sub Table] AS
(

SELECT   [dt]
        ,[search_time]
        ,[pervious search time] = LAG(search_time) OVER (ORDER BY search_time)
        ,[min diff] = ISNULL(DATEDIFF(MINUTE,LAG(search_time) OVER (ORDER BY search_time),search_time),0)
        ,[searches]
FROM     [search_session]
)
SELECT
    [dt],
    [search_time],
    [pervious search time],
    [min diff],
    [searches]
FROM [Sub Table]

得到了这个:

+----------+-------------------------+-------------------------+----------+----------+
|    dt    |       search_time       |  pervious search time   | min diff | searches |
+----------+-------------------------+-------------------------+----------+----------+
| 20200601 | 2020-06-01 00:36:38.000 | NULL                    |        0 |        1 |
| 20200601 | 2020-06-01 00:37:38.000 | 2020-06-01 00:36:38.000 |        1 |        1 |
| 20200601 | 2020-06-01 00:39:18.000 | 2020-06-01 00:37:38.000 |        2 |        1 |
| 20200601 | 2020-06-01 01:16:18.000 | 2020-06-01 00:39:18.000 |       37 |        1 |
| 20200601 | 2020-06-01 03:56:38.000 | 2020-06-01 01:16:18.000 |      160 |        1 |
| 20200601 | 2020-06-01 05:36:38.000 | 2020-06-01 03:56:38.000 |      100 |        1 |
| 20200601 | 2020-06-01 05:37:38.000 | 2020-06-01 05:36:38.000 |        1 |        1 |
| 20200601 | 2020-06-01 05:39:38.000 | 2020-06-01 05:37:38.000 |        2 |        1 |
| 20200601 | 2020-06-01 05:41:38.000 | 2020-06-01 05:39:38.000 |        2 |        1 |
| 20200601 | 2020-06-01 07:26:38.000 | 2020-06-01 05:41:38.000 |      105 |        1 |
+----------+-------------------------+-------------------------+----------+----------+

我想到了两种继续的可能性:

    使用窗口函数,如 RANK(),我可以对行进行分区,但我不知道如何使用条件来设置 PARTITION BY caluse。

    使用 WHILE 循环迭代表 - 再次发现很难形成 ths

【问题讨论】:

【参考方案1】:

这不能仅使用窗口函数来完成。您需要某种迭代过程,跟踪每个组的第一行,并动态识别下一行。

在 SQL 中,您可以使用递归查询来表达这一点:

with 
    data as (select t.*, row_number() over(order by search_time) rn from mytable t),
    cte as (
        select d.*, search_time as first_search_time 
        from data d
        where rn = 1
        union all 
        select d.*, 
            case when d.search_time > dateadd(minute, 5, c.first_search_time)
                then d.search_time
                else c.first_search_time
            end
        from cte c 
        inner join data d on d.rn = c.rn + 1
    )
select c.*, dense_rank() over(order by first_search_time) grp 
from cte c

对于您的示例数据,this returns

dt |搜索时间 |搜索 | rn | first_search_time | grp :--------- | :------------------------ | --------: | -: | :------------------------ | --: 2020-06-01 | 2020-06-01 00:36:38.000 | 1 | 1 | 2020-06-01 00:36:38.000 | 1 2020-06-01 | 2020-06-01 00:37:38.000 | 1 | 2 | 2020-06-01 00:36:38.000 | 1 2020-06-01 | 2020-06-01 00:39:18.000 | 1 | 3 | 2020-06-01 00:36:38.000 | 1 2020-06-01 | 2020-06-01 01:16:18.000 | 1 | 4 | 2020-06-01 01:16:18.000 | 2 2020-06-01 | 2020-06-01 03:56:38.000 | 1 | 5 | 2020-06-01 03:56:38.000 | 3 2020-06-01 | 2020-06-01 05:36:38.000 | 1 | 6 | 2020-06-01 05:36:38.000 | 4 2020-06-01 | 2020-06-01 05:37:38.000 | 1 | 7 | 2020-06-01 05:36:38.000 | 4 2020-06-01 | 2020-06-01 05:39:38.000 | 1 | 8 | 2020-06-01 05:36:38.000 | 4 2020-06-01 | 2020-06-01 05:41:38.000 | 1 | 9 | 2020-06-01 05:36:38.000 | 4 2020-06-01 | 2020-06-01 07:26:38.000 | 1 | 10 | 2020-06-01 07:26:38.000 | 5

【讨论】:

甜蜜!这是一个非常优雅的解决方案。我会进一步研究这种技术。

以上是关于按累积时间间隔将行分组的主要内容,如果未能解决你的问题,请参考以下文章

按日期分组的 MySQL 累积总和

如何使用 sql 和按日期分组显示指标在日期上的累积增长?

尝试将行附加到按对象分组中的每个组时的奇怪行为

MySQL查询根据按月分组的激活日期计算累积用户数

将时间间隔从文本转换为数字并按客户时间间隔分组

您如何按任何基于时间的间隔进行分组?