【问题标题】:Avoiding gaps in datetime intervals with CTE and start and end datetimes使用 CTE 以及开始和结束日期时间避免日期时间间隔的间隙
【发布时间】:2012-09-07 21:01:00
【问题描述】:

由于某种原因,我在使用此查询时发现我的时间间隔存在差距。仅使用基本数据时,我就可以使用它。但是,当加入我的表并指定 WHERE 子句时,我看到我的时间间隔有间隙。我还需要将 S.SessionEndTime 合并到我的时间间隔中,以查找与 ResponseTime 和 SessionEndTime 之间的给定 1 分钟时间间隔重叠的记录计数。

这是我正在使用的查询。通过使用派生表,我在 1 分钟间隔内基于 COUNT 获得每小时 MAX。

    DECLARE @start_date     DATETIME
DECLARE @end_date       DATETIME
DECLARE @interval TIME

SET @start_date = '08/01/2012'
SET @end_date = '08/01/2012 12:00:00'
SET @interval = '00:01:00.00';

    WITH TimeWindowsCTE AS 
    ( 
        SELECT 
            @start_date AS WindowStart, @start_date + @interval AS WindowEnd 
        UNION ALL 
        SELECT 
            SW.WindowStart + @Interval, SW.WindowEnd + @interval 
        FROM 
            TimeWindowsCTE AS SW 
        WHERE 
            SW.WindowEnd < @end_date 
    ) 
    SELECT 
        DATEADD(hour, DATEDIFF(hour, CONVERT(datetime, '19000101', 112), t1.WindowStart), CONVERT(datetime, '19000101', 112)) WindowStart, 
        MAX([Sessions]) [Sessions]
    FROM 
    (
        SELECT 
            SW.WindowStart, COUNT(*) [Sessions] 
        FROM 
            TimeWindowsCTE AS SW 
            LEFT OUTER JOIN SessionDetails AS S ON SW.WindowStart <= S.ResponseTime and S.ResponseTime < SW.WindowEnd
            --I need to introduce the S.SessionEndTime into the JOIN so I can count the number of records that 
            --fall within the interval
            LEFT OUTER JOIN VoipDetails V on S.SessionIdTime = V.SessionIdTime and S.SessionIdSeq = V.SessionIdSeq                      
            WHERE V.ToGatewayId IS NOT NULL
        GROUP BY 
            SW.WindowStart
    ) t1
    GROUP BY 
        DATEADD(hour, DATEDIFF(hour, CONVERT(datetime, '19000101', 112), t1.WindowStart), CONVERT(datetime, '19000101', 112))
    ORDER BY 
        DATEADD(hour, DATEDIFF(hour, CONVERT(datetime, '19000101', 112), t1.WindowStart), CONVERT(datetime, '19000101', 112))
    OPTION (maxrecursion 0)

这是我得到的输出

日期会议 2012-08-01 00:00:00.000 5 2012-08-01 01:00:00.000 3 2012-08-01 02:00:00.000 2 2012-08-01 03:00:00.000 2 2012-08-01 05:00:00.000 1 2012-08-01 06:00:00.000 2 2012-08-01 07:00:00.000 3 2012-08-01 08:00:00.000 2 2012-08-01 09:00:00.000 2 2012-08-01 10:00:00.000 1

对于 04:00:00 时间段,我需要一个 0。我想要完成的是确定在该给定小时内发生了多少并发会话,而不是在一小时的顶部有多少会话。这就是重叠发挥作用的地方。如果在上午 8 点 56 分开始通话并在上午 9 点 26 分结束,则应该在上午 8 点钟至少有 1 个电话,在上午 9 点钟至少有 1 个电话。然后根据一天中每个小时的并发会话数,我找到给定日期的 MAX,然后在我的报告中使用它。

这是数据的基本结构:

插入@Sessions ( SessionIdSeq, ResponseTime, SessionEndTime ) 值 (1, '20120901 00:00:00', '20120901 05:59:59' ), -- 单个会话中的几个小时。 (1, '20120901 01:01:00', '20120901 01:01:30' ), -- 各种重叠... (1, '20120901 01:02:00', '20120901 01:03:30' ), -- ... 一个小时内的会话。 (1, '20120901 00:00:05.077', '20120901 00:04:02.280'), (1, '20120901 00:00:14.687', '20120901 00:06:05.947'), (1, '20120901 00:00:17.857', '20120901 00:07:34.757'), (1, '20120901 00:00:25.843', '20120901 00:07:38.720'), (1, '20120901 00:00:29.427', '20120901 00:01:58.180'), (1, '20120901 00:00:31.853', '20120901 00:05:10.733'), (1, '20120901 00:00:40.693', '20120901 00:00:44.237'), (1, '20120901 00:00:58.773', '20120901 00:06:14.667'), (1, '20120901 00:00:59.457', '20120901 00:01:01.310'), (1, '20120901 00:01:16.390', '20120901 00:11:18.383')

【问题讨论】:

  • 嗯,CTE 看起来很眼熟。你能展示一个输入和输出的例子,并指出似乎有什么问题吗?以及您期望 GROUP BYORDER BY 子句执行的操作。
  • 您的各种转换似乎旨在从日期/时间确定“时间的顶部”。将其放入一个明确命名的函数中将使代码更容易解​​释。我也会避免与文本之间的转换,而是使用更像:declare @Now as DateTime = GetDate(); select @Now as 'Now', DateAdd( hour, DatePart( hour, @Now ), Cast( Cast( @Now as Date ) as DateTime ) ) as 'Top Of The Hour'.
  • 添加了我得到的输出以及我想从中得到什么。谢谢哈宝!
  • 请求澄清:您是在寻找 (A) 每小时并发 会话的最大数量,还是 (B) 总数每小时活跃的会话数?例如,如果一个长会话从 8:00:00 开始并在 10:30:00 结束,而 42 个短会话从 9:nn:05 开始并在 9:nn:20 结束(其中 nn 是 01 到 42) , 那么方法 (A) 返回 8:00 1, 9:00 2, 10:00 1。方法 (B) 将返回 8:00 1, 9:00 43, 10:00 1。
  • 查找每小时的最大并发会话数。这涉及许可证问题,它围绕并发会话展开。我有两份报告,一份报告包含一整天的最大并发会话数,第二份报告包含当天每小时的并发会话数

标签: tsql date common-table-expression overlapping


【解决方案1】:

好的,这里缺少说明的是一些 TSQL 计算两者: - 每小时活跃的会话总数和 - 每小时活动的最大并发会话数。

编辑:已使用更新问题中的示例数据,显示并发会话的最后一个查询的输出现在包括会话 ID,并且更正了先前优化中的错误 大大提高了性能。

注意:当SessionId 值因行而异时,这些查询效果最佳。对所有行使用1 值会导致令人失望的结果。因此SessionId 列上的IDENTITY 属性。

-- Parameters.
declare @Start as DateTime = '20120901 00:00:00'
declare @End as DateTime = '20120901 12:00:00'
declare @Interval as Time = '01:00:00.00' -- One hour.
select @Start as [Start], @End as [End], @Interval as [Interval]

-- Sample data.
declare @Sessions as Table ( SessionId Int Identity, SessionStart DateTime, SessionEnd DateTime )
insert into @Sessions ( SessionStart, SessionEnd ) values
  ( '20120901 00:00:00', '20120901 05:59:59' ), -- Several hours in a single session.
  ( '20120901 01:01:00', '20120901 01:01:30' ), -- An assortment of overlapping ... 
  ( '20120901 01:02:00', '20120901 01:03:30' ), -- ... sessions during a single hour. 
  ( '20120901 00:00:05.077', '20120901 00:04:02.280' ),
  ( '20120901 00:00:14.687', '20120901 00:06:05.947' ),
  ( '20120901 00:00:17.857', '20120901 00:07:34.757' ),
  ( '20120901 00:00:25.843', '20120901 00:07:38.720' ),
  ( '20120901 00:00:29.427', '20120901 00:01:58.180' ),
  ( '20120901 00:00:31.853', '20120901 00:05:10.733' ),
  ( '20120901 00:00:40.693', '20120901 00:00:44.237' ),
  ( '20120901 00:00:58.773', '20120901 00:06:14.667' ),
  ( '20120901 00:00:59.457', '20120901 00:01:01.310' ),
  ( '20120901 00:01:16.390', '20120901 00:11:18.383' )
select * from @Sessions 

-- Summary of sessions active at any time during each hour. 
; with SampleWindows as ( 
  select @Start as WindowStart, @Start + @Interval as WindowEnd 
  union all 
  select SW.WindowStart + @Interval, SW.WindowEnd + @Interval 
    from SampleWindows as SW 
    where SW.WindowEnd < @End 
  ) 
  select SW.WindowStart, Count( S.SessionStart ) as [Sessions] 
    from SampleWindows as SW left outer join 
      @Sessions as S on SW.WindowStart <= S.SessionEnd and S.SessionStart < SW.WindowEnd 
    group by SW.WindowStart 

-- Summary of maximum concurrent sessions active during each hour. 
; with SampleWindows as ( 
  select 1 as SampleWindowId, @Start as WindowStart, @Start + @Interval as WindowEnd 
  union all 
  select SW.SampleWindowId + 1, SW.WindowStart + @Interval, SW.WindowEnd + @Interval 
    from SampleWindows as SW 
    where SW.WindowEnd < @End 
  ), 
  ActiveSessionsDuringWindow as ( 
  select SW.SampleWindowId, SW.WindowStart, SW.WindowEnd, S.SessionId, S.SessionStart, S.SessionEnd, 
    -- A "pane" is the more restrictive of the window and the session start/end times. 
    case when SW.WindowStart <= S.SessionStart then S.SessionStart else SW.WindowStart end as PaneStart, 
    case when SW.WindowEnd >= S.SessionEnd then S.SessionEnd else SW.WindowEnd end as PaneEnd
    from SampleWindows as SW left outer join 
      @Sessions as S on SW.WindowStart <= S.SessionEnd and S.SessionStart < SW.WindowEnd 
  ), 
  ConcurrentSearch as ( 
  select SampleWindowId, WindowStart, WindowEnd, SessionId, SessionStart, SessionEnd, PaneStart, PaneEnd, 
    Cast( '|' + Right( Replicate( '0', 3 ) + Cast( SessionId as VarChar(4) ), 4 ) + '|' as VarChar(1024) ) as SessionIds, 
    Cast( case when SessionId is NULL then 0 else 1 end as Int ) as Sessions 
    from ActiveSessionsDuringWindow 
  union all 
  select CS.SampleWindowId, CS.WindowStart, CS.WindowEnd, ASDW.SessionId, CS.SessionStart, CS.SessionEnd, 
    case when CS.PaneStart <= ASDW.PaneStart then ASDW.PaneStart else CS.PaneStart end as PaneStart, 
    case when CS.PaneEnd >= ASDW.PaneEnd then ASDW.PaneEnd else CS.PaneEnd end as PaneEnd, 
    Cast( CS.SessionIds + Right( Replicate( '0', 3 ) + Cast( ASDW.SessionId as VarChar(4) ), 4 ) + '|' as VarChar(1024) ), 
    CS.Sessions + 1 
    from ConcurrentSearch as CS inner join 
      ActiveSessionsDuringWindow as ASDW on ASDW.SampleWindowId = CS.SampleWindowId and 
        -- We haven't visited this session along this path. 
        CS.SessionId < ASDW.SessionId and -- EDIT: Reduce the size of the search tree. 
        CharIndex( '|' + Right( Replicate( '0', 3 ) + Cast( ASDW.SessionId as VarChar(4) ), 4 ) + '|', CS.SessionIds ) = 0 and 
        -- The session's pane overlaps the concurrent search pane. 
        CS.PaneStart <= ASDW.PaneEnd and ASDW.PaneStart <= CS.PaneEnd 
  )
  select WindowStart, Max( Sessions ) as Sessions,
    ( select top 1 SessionIds from ConcurrentSearch where Sessions = Max( CS.Sessions ) ) as SessionIds
    from ConcurrentSearch as CS
    group by WindowStart 

以下是最后一个查询的变体,它不使用 @Sessions 表中的行 ID 值。相反,它使用Row_Number() 在查询期间分配合适的值。这也将SessionId 值不超过四位数的假设更改为在任何给定小时内活动的会话不超过 9,999 个的假设。

-- Summary of maximum concurrent sessions active during each hour.
; with SampleWindows as (  
  select 1 as SampleWindowId, @Start as WindowStart, @Start + @Interval as WindowEnd  
  union all  
  select SW.SampleWindowId + 1, SW.WindowStart + @Interval, SW.WindowEnd + @Interval  
    from SampleWindows as SW  
    where SW.WindowEnd < @End  
  ),  
  ActiveSessionsDuringWindow as (  
  select SW.SampleWindowId, SW.WindowStart, SW.WindowEnd, S.SessionStart, S.SessionEnd,  
    -- A "pane" is the more restrictive of the window and the session start/end times.  
    case when SW.WindowStart <= S.SessionStart then S.SessionStart else SW.WindowStart end as PaneStart,  
    case when SW.WindowEnd >= S.SessionEnd then S.SessionEnd else SW.WindowEnd end as PaneEnd,
    Row_Number() over ( partition by SW.SampleWindowId order by S.SessionStart ) as SampleId
    from SampleWindows as SW left outer join  
      @Sessions as S on SW.WindowStart <= S.SessionEnd and S.SessionStart < SW.WindowEnd  
  ),  
  ConcurrentSearch as (  
  select SampleWindowId, WindowStart, WindowEnd, SampleId, SessionStart, SessionEnd, PaneStart, PaneEnd,  
    Cast( '|' + Right( Replicate( '0', 3 ) + Cast( SampleId as VarChar(4) ), 4 ) + '|' as VarChar(1024) ) as SampleIds,  
    Cast( case when SampleId is NULL then 0 else 1 end as Int ) as Sessions  
    from ActiveSessionsDuringWindow  
  union all  
  select CS.SampleWindowId, CS.WindowStart, CS.WindowEnd, ASDW.SampleId, CS.SessionStart, CS.SessionEnd,  
    case when CS.PaneStart <= ASDW.PaneStart then ASDW.PaneStart else CS.PaneStart end as PaneStart,  
    case when CS.PaneEnd >= ASDW.PaneEnd then ASDW.PaneEnd else CS.PaneEnd end as PaneEnd,  
    Cast( CS.SampleIds + Right( Replicate( '0', 3 ) + Cast( ASDW.SampleId as VarChar(4) ), 4 ) + '|' as VarChar(1024) ),  
    CS.Sessions + 1  
    from ConcurrentSearch as CS inner join  
      ActiveSessionsDuringWindow as ASDW on ASDW.SampleWindowId = CS.SampleWindowId and  
        -- We haven't visited this session along this path.  
        CS.SampleId < ASDW.SampleId and -- EDIT: Reduce the size of the search tree.  
        CharIndex( '|' + Right( Replicate( '0', 3 ) + Cast( ASDW.SampleId as VarChar(4) ), 4 ) + '|', CS.SampleIds ) = 0 and  
        -- The session's pane overlaps the concurrent search pane.  
        CS.PaneStart <= ASDW.PaneEnd and ASDW.PaneStart <= CS.PaneEnd  
  ) 
  select WindowStart, Max( Sessions ) as Sessions
    from ConcurrentSearch as CS 
    group by WindowStart  

这应该很容易修改以针对现有表运行。 SessionStart 升序、SessionEnd 升序上的单个索引应该会提高性能。

【讨论】:

  • 我已经更新了帖子以包含数据中的数据的实际外观
  • 我正在用实际数据测试这个查询。看起来如果它在给定小时内处理接近 100 的行,它不会完成执行。几乎就像它在一个无限循环中。有什么想法吗?
  • 在昨天的编辑中,我更正了 CS.SessionId &lt; ASDW.SessionId and -- EDIT: Reduce the size of the search tree 行。在我的测试中,搜索树的大小减少了大约 1,000 倍。您输入的行是否具有唯一的标识值?如编辑中所述,将所有 SessionIdSeq 值设置为 1 会导致问题。
  • 我所做的是获取您的表@Sessions 并用我的数据中的实际值填充它。执行 INSERT INTO .. SELECT... 这可以使您的大部分查询保持完整。
  • 对我来说奇怪的是,我尝试使用永久表而不是变量表来希望获得更好的性能,但我得到了不同的结果。在这种情况下,最大会话数始终为 0 或 1。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-05
  • 1970-01-01
  • 2020-03-21
  • 1970-01-01
相关资源
最近更新 更多