【问题标题】:Group table into 15 minute intervals将表格分组为 15 分钟间隔
【发布时间】:2012-11-18 21:05:42
【问题描述】:

T-SQL、SQL Server 2008 及更高版本

给定一个样本表

 StatusSetDateTime   | UserID | Status    | StatusEndDateTime   | StatusDuration(in seconds)
============================================================================
 2012-01-01 12:00:00 | myID   | Available | 2012-01-01 13:00:00 | 3600

我需要将其分解为使用 15 分钟间隔的视图,例如:

IntervalStart       | UserID | Status | Duration

===========================================

2012-01-01 12:00:00 | myID | Available | 900 

2012-01-01 12:15:00 | myID | Available | 900

2012-01-01 12:30:00 | myID | Available | 900 

2012-01-01 12:45:00 | myID | Available | 900 

2012-01-01 13:00:00 | myID | Available | 0

etc....

现在我已经能够四处搜索并找到一些会崩溃的查询 我为 MySql Here 找到了类似的东西:

还有一些用于 T-SQL 的东西Here

但在第二个示例中,他们将结果相加,而我需要将总持续时间除以间隔时间(900 秒)除以用户除以状态。

我能够调整第二个链接中的示例以将所有内容拆分为间隔,但返回总持续时间,我无法完全弄清楚如何拆分间隔持续时间(并且仍然总计为原始总持续时间)。

提前感谢您提供任何见解!

编辑:第一次尝试

 ;with cte as 
    (select MIN(StatusDateTime) as MinDate
          , MAX(StatusDateTime) as MaxDate
          , convert(varchar(14),StatusDateTime, 120) as StartDate
          , DATEPART(minute, StatusDateTime) /15 as GroupID
          , UserID
          , StatusKey
          , avg(StateDuration) as AvgAmount
     from AgentActivityLog
     group by convert(varchar(14),StatusDateTime, 120)
         , DATEPART(minute, StatusDateTime) /15
         , Userid,StatusKey)

  select dateadd(minute, 15*GroupID, CONVERT(datetime,StartDate+'00'))
         as [Start Date]
       , UserID, StatusKey, AvgAmount as [Average Amount]
  from cte

编辑:第二次尝试

;With cte As
   (Select DateAdd(minute
                   , 15 * (DateDiff(minute, '20000101', StatusDateTime) / 15)
                   , '20000101') As StatusDateTime
         , userid, statuskey, StateDuration
    From AgentActivityLog)

 Select StatusDateTime, userid,statuskey,Avg(StateDuration)
 From cte
 Group By StatusDateTime,userid,statuskey;

【问题讨论】:

  • 在您的示例中,您已拆分为 5 行和 5 * 900 != 3600 还是我遗漏了什么?
  • 您的第一个间隔总是从 StatusSetDateTime 开始,还是总是基于 :00, :15, :30, :45?使用您的示例,如果 StatusSetDateTime 为 12:06:30,您的第一个时间间隔是从 12;00:00 还是 12:06:30 开始?另外,您可以发布您目前用于拆分的 SQL 吗?
  • @FarukSahin 抱歉,我的意思是在最后的间隔中输入 0,感谢您理解。间隔将始终为 00、15、30、45
  • 就个人而言,StatusEndDateTime(真的吗?)是独家 - 你在13:00 拥有的状态是不是 Available,这是什么其他(未知,或可能没有)。毕竟,它会建议您再有 15 分钟的空闲时间,但实际上可能并非如此。
  • 抱歉,如果我重复一遍,不清楚:StatusSetDatetime 总是 是否会以 :00、:15、:30 或 :45 结尾?没有任何秒部分?而且,您确定要包含一个持续时间为 0 的最后一行吗?持续时间为 0 的行实际上具有什么价值或业务意义?

标签: sql sql-server sql-server-2008 tsql group-by


【解决方案1】:
;with cte_max as 
(
   select dateadd(mi, -15, max(StatusEndDateTime)) as EndTime, min(StatusSetDateTime) as StartTime
   from AgentActivityLog
), times as
(
    select StartTime as Time from cte_max
    union all
    select dateadd(mi, 15, c.Time)
    from times as c
        cross join cte_max as cm
    where c.Time <= cm.EndTime
)
select
    t.Time, A.UserID, A.Status,
    case
        when t.Time = A.StatusEndDateTime then 0
        else A.StatusDuration / (count(*) over (partition by A.StatusSetDateTime, A.UserID, A.Status) - 1)
    end as Duration
from AgentActivityLog as A
    left outer join times as t on t.Time >= A.StatusSetDateTime and t.Time <= A.StatusEndDateTime

sql fiddle demo

【讨论】:

  • 感谢您的帮助 - 我在 SSMS 中运行时收到一个 maxrecursion 错误,在 SQL Fiddle 中似乎可以正常工作。唯一的问题是,对于 myID2 - 不可用,您的持续时间为 320,即使它们在整个 15 分钟内都处于该状态,应该是 900
  • 是的,好的,这是因为我添加了一个错误的持续时间 - 1600 而应该是 4500。看来持续时间字段是多余的,你可以写 900 而不是 A.StatusDuration / (count(*) over (partition by A.StatusSetDateTime, A.UserID, A.Status) - 1) 并删除从你的桌子上的持续时间:) sqlfiddle.com/#!3/8c921/1
  • @Wjdavis5 如果开始日期和结束日期之间的差异始终为 1 小时,我也有超级简单的解决方案
  • 再次感谢您为此工作,我的持续时间并不总是 1 小时,这只是一个简单的例子。
【解决方案2】:

我从不习惯使用日期数学将事物分成多个分区。似乎有各种各样的陷阱。

我更喜欢做的是创建一个表(预定义的、表值函数、表变量),其中每个日期分区范围都有一行。表值函数方法特别有用,因为您可以根据需要为任意范围和分区大小构建它。然后,您可以加入此表以拆分内容。

paritionid starttime     endtime
---------- ------------- -------------
1          8/1/2012 5:00 8/1/2012 5:15
2          8/1/2012 5:15 8/1/2012 5:30
...

我无法评价这种方法的性能,但我发现查询更加直观。

【讨论】:

  • P.S.您不必说“陷入陷阱”。 “陷阱”本身就足够了!一个人如何处理一个陷阱,但是,好,落入其中? :) 我的祖母过去常说“你要一起去吗?”而不是“你要去吗?”,这是一个类似的东西。
  • 每当有人编辑我的工作草稿时,他们都会删减 5% 到 10% 的文字。如果我相信前世,我会相信在前世,我是被言词所付的。没错。
  • 我希望你明白我喜欢这种语言,而不是试图从根本上批评。 :)
【解决方案3】:

如果您有一个每 15 分钟时间戳的帮助表,则相对简单,您可以通过 BETWEEN 将其加入到您的基表中。您可以即时构建帮助表或将其永久保存在数据库中。你公司的下一个人也很容易弄清楚:

// declare a table and a timestamp variable
declare @timetbl table(t datetime)
declare @t datetime

// set the first timestamp
set @t = '2012-01-01 00:00:00'

// set the last timestamp, can easily be extended to cover many years
while @t <= '2013-01-01'
begin
    // populate the table with a new row, every 15 minutes
    insert into @timetbl values (@t)
    set @t = dateadd(mi, 15, @t)
end


// now the Select query:
select 
   tt.t, aal.UserID, aal.Status,
   case when aal.StatusEndDateTime <= tt.t then 0 else 900 end as Duration
   // using a shortcut for Duration, based on your comment that Start/End are always on the quarter-hour, and thus always 900 seconds or zero

from 
   @timetbl tt 
      INNER JOIN AgentActivityLog aal 
         on tt.t between aal.StatusSetDateTime and aal.StatusEndDateTime

order by
  aal.UserID, tt.t

【讨论】:

    【解决方案4】:

    您可以使用 recursive Common Table Expression,在 StatusEndDateTime 大于 IntervalStart 时继续添加持续时间,例如

    ;with cte as (
        select StatusSetDateTime as IntervalStart
            ,UserID
            ,Status
            ,StatusDuration/(datediff(mi, StatusSetDateTime, StatusEndDateTime)/15) as Duration
            , StatusEndDateTime
        From AgentActivityLog
        Union all
        Select DATEADD(ss, Duration, IntervalStart) as IntervalStart
            , UserID
            , Status
            , case when DATEADD(ss, Duration, IntervalStart) = StatusEndDateTime then 0 else Duration end as Duration
            , StatusEndDateTime
        From cte
        Where IntervalStart < StatusEndDateTime
    )
    
    select IntervalStart, UserID, Status, Duration from cte
    

    【讨论】:

      【解决方案5】:

      这是一个无需辅助表即可为您完成工作的查询。 (我不反对辅助表,它们很有用,我使用它们。有时也可以不使用它们。)此查询允许活动在任何时间开始和结束,即使不是以 :00 结尾的整分钟, :15,:30,:45。如果会有毫秒部分,那么您将不得不做一些实验,因为按照您的模型,我只达到了第二个分辨率。

      如果您有一个已知的硬最大持续时间,则删除 @MaxDuration 并将其替换为该值(以分钟为单位)。 N &lt;= @MaxDuration 对查询执行良好至关重要。

      DECLARE @MaxDuration int;
      SET @MaxDuration = (SELECT Max(StatusDuration) / 60 FROM #AgentActivityLog);
      
      WITH
      L0 AS(SELECT 1 c UNION ALL SELECT 1),
      L1 AS(SELECT 1 c FROM L0, L0 B),
      L2 AS(SELECT 1 c FROM L1, L1 B),
      L3 AS(SELECT 1 c FROM L2, L2 B),
      L4 AS(SELECT 1 c FROM L3, L3 B),
      L5 AS(SELECT 1 c FROM L4, L4 B),
      Nums AS(SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) n FROM L5)
      SELECT
         S.IntervalStart,
         Duration = DateDiff(second, S.IntervalStart, E.IntervalEnd)
      FROM
         #AgentActivityLog L
         CROSS APPLY (
            SELECT N, Offset = (N.N - 1) * 900
            FROM Nums N
            WHERE N <= @MaxDuration
         ) N
         CROSS APPLY (
            SELECT Edge =
               DateAdd(second, N.Offset, DateAdd(minute,
                  DateDiff(minute, '20000101', L.StatusSetDateTime)
                  / 15 * 15, '20000101')
               )
         ) G
         CROSS APPLY (
            SELECT IntervalStart = Max(T.BeginTime)
            FROM (
               SELECT L.StatusSetDateTime
               UNION ALL SELECT G.Edge
            ) T (BeginTime)
         ) S
         CROSS APPLY (
            SELECT IntervalEnd = Min(T.EndTime)
            FROM (
               SELECT L.StatusEndDateTime
               UNION ALL SELECT G.Edge + '00:15:00'
            ) T (EndTime)
         ) E
      WHERE
         N.Offset <= L.StatusDuration
      ORDER BY
         L.StatusSetDateTime,
         S.IntervalStart;
      

      如果你想试试,这里是安装脚本:

      CREATE TABLE #AgentActivityLog (
          StatusSetDateTime datetime,
          StatusEndDateTime datetime,
          StatusDuration AS (DateDiff(second, 0, StatusEndDateTime - StatusSetDateTime))
      );
      
      INSERT #AgentActivityLog -- weird end times
      SELECT '20120101 12:00:00', '20120101 13:00:00'
      UNION ALL SELECT '20120101 13:00:00', '20120101 13:27:56'
      UNION ALL SELECT '20120101 13:27:56', '20120101 13:28:52'
      UNION ALL SELECT '20120101 13:28:52', '20120120 11:00:00'
      
      INSERT #AgentActivityLog -- 15-minute quantized end times
      SELECT '20120101 12:00:00', '20120101 13:00:00'
      UNION ALL SELECT '20120101 13:00:00', '20120101 13:30:00'
      UNION ALL SELECT '20120101 13:30:00', '20120101 14:00:00'
      UNION ALL SELECT '20120101 14:00:00', '20120120 11:00:00'
      

      另外,这里有一个版本,它只要求整分钟以 :00、:15、:30 或 :45 结尾的时间。

      DECLARE @MaxDuration int;
      SET @MaxDuration = (SELECT Max(StatusDuration) / 60 FROM #AgentActivityLog);
      
      WITH
      L0 AS(SELECT 1 c UNION ALL SELECT 1),
      L1 AS(SELECT 1 c FROM L0, L0 B),
      L2 AS(SELECT 1 c FROM L1, L1 B),
      L3 AS(SELECT 1 c FROM L2, L2 B),
      L4 AS(SELECT 1 c FROM L3, L3 B),
      L5 AS(SELECT 1 c FROM L4, L4 B),
      Nums AS(SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) n FROM L5)
      SELECT
         S.IntervalStart,
         Duration = CASE WHEN Offset = StatusDuration THEN 0 ELSE 900 END
      FROM
         #AgentActivityLog L
         CROSS APPLY (
            SELECT N, Offset = (N.N - 1) * 900
            FROM Nums N
            WHERE N <= @MaxDuration
         ) N
         CROSS APPLY (
            SELECT IntervalStart = DateAdd(second, N.Offset, L.StatusSetDateTime)
         ) S
      WHERE
         N.Offset <= L.StatusDuration   
      ORDER BY
         L.StatusSetDateTime,
         S.IntervalStart;
      

      看起来最后的 0 Duration 行确实不正确,因为那样你就不能只按 IntervalStart 排序,因为有重复的 IntervalStart 值。使总数加 0 的行有什么好处?

      【讨论】:

        猜你喜欢
        • 2012-04-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-17
        • 2022-01-07
        • 1970-01-01
        • 2011-02-17
        • 1970-01-01
        相关资源
        最近更新 更多