SQL 按日期时间分组,最大差异为 x 分钟

Posted

技术标签:

【中文标题】SQL 按日期时间分组,最大差异为 x 分钟【英文标题】:SQL grouping by datetime with a maximum difference of x minutes 【发布时间】:2016-08-09 13:53:55 【问题描述】:

我在 MS SQL Server 中对我的数据集进行分组时遇到问题。

我的桌子看起来像

 # | CustomerID | SalesDate           | Turnover
---| ---------- | ------------------- | ---------
 1 | 1          | 2016-08-09 12:15:00 |  22.50
 2 | 1          | 2016-08-09 12:17:00 |  10.00
 3 | 1          | 2016-08-09 12:58:00 |  12.00
 4 | 1          | 2016-08-09 13:01:00 |  55.00
 5 | 1          | 2016-08-09 23:59:00 |  10.00
 6 | 1          | 2016-08-10 00:02:00 |   5.00

现在我想将 SalesDate 与下一行的差异最大为 5 分钟的行分组。 所以第 1 行和第 2 行、第 3 行和第 4 行和第 5 行和第 6 行各为一组。

我的方法是使用 DATEPART() 函数获取分钟数并将结果除以 5:

(DATEPART(MINUTE, SalesDate) / 5)

对于第 1 行和第 2 行,结果将是 3,并且在这里分组可以完美地工作。 但是对于SalesDate的小时甚至一天部分发生变化的其他行,结果不能用于分组。

所以这就是我卡住的地方。如果有人能指出我正确的方向,我将不胜感激。

【问题讨论】:

检查this或this 您可以编写一个查询,使用 LEAD 或 LAG 来检查下一行并找到时间差,然后使用 DENSE_RANK 分配一个分组号,按差异 > 5 分钟进行分区。然后按该分组号分组。如果您要在一个查询中针对多行编写它,这是一个密集的过程。 我认为这是一个很好的第一个问题。 【参考方案1】:

您希望根据它们之间的时间对相邻事务进行分组。这个想法是分配某种分组标识符,然后将其用于聚合。

这是一种方法:

使用lag() 和日期算术开始识别组。 做一个组的累计总和开始识别每个组。 聚合

查询如下所示:

select customerid, min(salesdate), max(saledate), sum(turnover)
from (select t.*,
             sum(case when salesdate > dateadd(minute, 5, prev_salesdate)
                      then 1 else 0
                 end) over (partition by customerid order by salesdate) as grp
      from (select t.*,
                   lag(salesdate) over (partition by customerid order by salesdate) as prev_salesdate
            from t
           ) t
     ) t
group by customerid, grp;

【讨论】:

太棒了!非常感谢,这非常有效,甚至非常快! :)【参考方案2】:

编辑

感谢@JoeFarrell 指出我回答了错误的问题。 OP 正在寻找行之间的动态时间差,但这种方法会创建固定的边界。

原答案

您可以创建一个时间表。这是一个包含一天中每一秒的一条记录的表。您的表格将有第二列,您可以使用它来执行分组。

CREATE TABLE [Time]
    (
        TimeId      TIME(0) PRIMARY KEY,
        TimeGroup   TIME
    )
;

-- You could use a loop here instead.
INSERT INTO [Time]
    (
        TimeId,
        TimeGroup
    )
VALUES
    ('00:00:00', '00:00:00'),    -- First group starts here.
    ('00:00:01', '00:00:00'),
    ('00:00:02', '00:00:00'),
    ('00:00:03', '00:00:00'),
    ...
    ('00:04:59', '00:00:00'),
    ('00:05:00', '00:05:00'),    -- Second group starts here.
    ('00:05:01', '00:05:00')
;

该方法在以下情况下效果最佳:

    您需要在几个不同的查询中重复使用您的自定义分组。 您有两个或多个您经常使用的自定义组。

填充后,您可以简单地加入表并输出所需的结果。

/* Using the time table.
 */
SELECT
    t.TimeGroup,
    SUM(Turnover) AS SumOfTurnover
FROM
    Sales AS s
        INNER JOIN [Time] AS t      ON t.TimeId = CAST(s.SalesDate AS Time(0))
GROUP BY
    t.TimeGroup
;

【讨论】:

我认为这行不通。如果您有两个时间分别为 00:04:59 和 00:05:00 的条目,即使它们相隔一秒,您的解决方案也会将它们分开分组。这与 OP 在他自己的第一次尝试中报告的相同缺陷。 是的,你是对的。我误解了这个问题。 OP 不想要硬编码的边界。相反,他们正在寻找动态差异。

以上是关于SQL 按日期时间分组,最大差异为 x 分钟的主要内容,如果未能解决你的问题,请参考以下文章

如果日期时间索引的差异小于熊猫系列的 5 分钟,则分组

Mysql按日期时间的日期部分分组,并为每个日期选择具有最大日期时间的行

按连续日期分组,忽略 SQL 中的周末

Mongodb,按日期差异分组并获取小时

SQL:按日期分组并对列中的值求和

在 SQL 查询中将日期舍入到当天