【问题标题】:SQL query to include time segments with no countsSQL 查询以包含没有计数的时间段
【发布时间】:2017-11-17 20:53:50
【问题描述】:

我在 SQL Server 2014 中工作。我有一个记录“计数”的表和计数的时间戳。计数周期是一个两小时的块,可以从任何一刻钟开始。在下面的示例数据中,计数从 16:00 开始,一直持续到 18:00。计数块可能在 01:30 开始并在 03:30 停止。

Timestamp   Count
16:00:31    1
16:00:42    1
16:16:04    1
16:16:06    1
16:45:10    1
16:45:31    1
16:45:32    1
17:16:45    1
17:16:52    1
17:16:53    1
17:33:19    1
17:34:01    1
17:45:03    1
17:46:08    1

我有一个查询,它对两个小时内 15 分钟间隔内的计数求和:

SELECT
    FORMAT(DATEPART(HOUR, [Timestamp]), '0#') + ':' + FORMAT(DATEPART(MINUTE, [TimeStamp]) / 15 * 15, '0#') AS QtrHrBeg
    , COUNT(*) AS CountTotal
FROM 
    [Sandbox].[trippetoe].[SURVEYCOUNTS]
GROUP BY 
    DATEPART(HOUR, [TIMESTAMP])
    , (DATEPART(MINUTE, [TIMESTAMP]) / 15 * 15)

结果如下:

QtrHrBeg    Count
16:00   2
16:15   2
16:45   3
17:15   3
17:30   2
17:45   2

我想包括没有计数的 15 分钟间隔 - 在本例中,从 16:30 和 17:00 开始的一刻钟,如下所示:

QtrHrBeg    Count
16:00   2
16:15   2
16:30   0
16:45   3
17:00   0
17:15   3
17:30   2
17:45   2

我该怎么做?

【问题讨论】:

    标签: sql sql-server tsql group-by


    【解决方案1】:

    见下文。

    首先创建一个包含当天所有时间间隔的时间表,然后将其限制为您想要的 2 小时窗口的时间间隔。

    然后将其加入数据表的总和,将 0 推入连接返回 null 的位置。

    DECLARE @Data TABLE ([TimeStamp] TIME, [Count] INT)
    INSERT INTO @Data ([TimeStamp],[Count])
    VALUES ('16:00:31',1),
           ('16:00:42',1),
           ('16:16:04',1),
           ('16:16:06',1),
           ('16:45:10',1),
           ('16:45:31',1),
           ('16:45:32',1),
           ('17:16:45',1),
           ('17:16:52',1),
           ('17:16:53',1),
           ('17:33:19',1),
           ('17:34:01',1),
           ('17:45:03',1),
           ('17:46:08',1)
    
    ;with AllIntervals AS
    (
        SELECT CONVERT(TIME,'00:00:00') AS Interval
        UNION ALL
        SELECT DATEADD(MINUTE,15,Interval)
        FROM AllIntervals
        WHERE Interval<'23:45:00'
    ), MyIntervals AS
        (
            SELECT CONVERT(VARCHAR(5),Interval,108) AS Interval
            FROM AllIntervals
            WHERE Interval >= (SELECT MIN(CONVERT(TIME,DATEADD(minute,(DATEDIFF(minute,0,[TimeStamp])/15)*15,0))) FROM @Data)
              AND Interval < DATEADD(HOUR,2,(SELECT MIN(CONVERT(TIME,DATEADD(minute,(DATEDIFF(minute,0,[TimeStamp])/15)*15,0))) FROM @Data))
        )
    
    SELECT M.Interval, ISNULL(I.[Count],0)
    FROM MyIntervals M
    LEFT JOIN (SELECT CONVERT(TIME,DATEADD(minute,(DATEDIFF(minute,0,[TimeStamp])/15)*15,0)) AS Interval, SUM([Count]) AS Count
               FROM @Data
               GROUP BY CONVERT(TIME,DATEADD(minute,(DATEDIFF(minute,0,[TimeStamp])/15)*15,0))) I
       ON M.Interval=I.Interval
    

    【讨论】:

    • 17:15 2 在 OP 所需的输出中,而它在您的输出中是 17:15 3
    • @Sami 检查他的源数据示例。里面有 3 个... 17:16:45、17:16:52 和 17:16:53
    • 我更新了 17:15 段的原始数据示例。
    • @JiggsJedi 我需要将 MyIntervals 中的 WHERE 语句更改为 AND Interval &lt;= DATEADD.... 以使用我的完整数据集。否则它的效果很好
    【解决方案2】:

    你可以使用下面的

    1. 在您要处理的数据中找到最小日期和最大日期,然后将这两个值四舍五入到最接近的 15
    2. 将片段分成 15 分钟间隔
    3. 左加入您的数据,结果出来并按 StartTime 应用组,我使用格式仅显示时间格式

    这种方法的好处是它可以在特定的时间间隔内工作,并且不会占用您数据范围之外的任何时间间隔。

    with initial as(
      select dateadd(minute, datediff(minute,0,min([Time])) / 15 * 15, 0) as MinTime,
             dateadd(minute, datediff(minute,0,max([Time])) / 15 * 15, 0) as MaxTime
      from data
     ), times as(
       select StartTime = MinTime,
              EndTime =dateadd(millisecond,-1,dateadd(minute,15,MinTime)),
              MaxTime
       from initial
       union all
       select dateadd(millisecond,1,EndTime),
              dateadd(minute,15,EndTime),
              MaxTime
       from times
       where EndTime<MaxTime
     )
    
     select format(t.StartTime,'HH:mm') as [Time],isnull(sum(d.[Count]),0) as [Count]
     from times t
     left join data d on d.[Time] between t.StartTime and t.EndTime
     group by t.StartTime 
    

    这是输出

    Time    Count
    16:00   2
    16:15   2
    16:30   0
    16:45   3
    17:00   0
    17:15   3
    17:30   2
    17:45   2
    

    这是一个工作的demo

    希望对你有帮助

    编辑

    我根据@HABO的评论把second的用法改成了millisecond,这样可以解决16:59:59这样的情况

    【讨论】:

    • 所有落入间隔之间一秒间隙的样本呢?使用half-open interval 可以解决问题并澄清代码:计算EndTime 时不要减去一秒,并使用... on t.StartTime &lt;= d.[Time] and d.[Time] &lt; t.EndTime ... 进行范围检查。
    • 感谢您的好评,我使用了millisecond,这对于上面的问题来说已经足够了,如果用户关心小时、分钟和秒,我相信是这样的,它可以提供很高的准确性跨度>
    • 这就是为什么我建议以明确的方式改变它。试试declare @Foo as DateTime = '2017-07-01 12:00:00.000'; select @Foo, DateAdd( millisecond, -1, @Foo );。然后阅读DateTime 的“准确性”。您需要减去 3 毫秒才能有所作为。
    • 我为你打招呼:)
    • 我应该在我的演示中使用DateTime2
    猜你喜欢
    • 2021-02-19
    • 1970-01-01
    • 2021-11-27
    • 1970-01-01
    • 2012-11-25
    • 2011-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多