通过聚合和分组接收新的格式化 table

Receive a new formatted table via aggregation and group by

我在这里遇到一个 SQL 服务器查询的大问题,我真的不知道如何继续。

目的是接收一个 table,区别于从 00:00 - 00:2923:30 - 23:59 的不同 time-intervals。在每个时间间隔中,我想总结在这些时间段内等待的实体的总分钟数。此信息可以由 starttimeendtime 以及实体的状态接收,如下所示:

startdate                    | finishdate                   | resourcestatus | id
2015-03-19 10:22:56.8490000  | 2015-03-19 10:32:56.8490000  | 8              | asdsdasdsad

如您所见,这样一个实体的状态可以从一个时间间隔 (10:00 - 10:30) 变为另一个时间间隔 (10:30 - 11:00)。

直到现在我通过定义 4 组 time-intervals 解决了这个问题(完成和开始都在间隔内,开始在间隔内但结束在,开始在间隔内但结束在间隔内,开始和结束都在间隔内interval) 这 4 个组由 time-intervals.

加入

我会 post 这里的代码,但它太多了。我的结果看起来像这样。以下是查询不同部分的开头:

select  zr.nr, 
        zr.interval, 
        case when outOfInterval.waittime is not null
                    then SUM(outOfInterval.waittime) 
                    else 0
               end 
               + 
               case when inInterval.waittime is not null 
                    then SUM(inInterval.waittime)
                    else 0
               end 
               +
               case when startInInterval.waittime is not null 
                    then SUM(startInInterval.waittime) 
                    else 0
               end 
               +
               case when finishInInterval.waittime is not null 
                    then sum(finishInInterval.waittime)
                    else 0
               end
               as waitingMinutes
From    (select 1 as nr,'00:00 - 00:29' as interval, 0 as waittime
        union  select 2,'00:30 - 00:59', 0 
        union  select 3,'01:00 - 01:29', 0 ...
        ) zr
left join (select case when CONVERT(time, rt.startedat, 8) < '00:00' and CONVERT(time, rt.finishedat , 8) > '00:30'  then '00:00 - 00:29' end as inter, 30 as waittime from T_resourcetracking rt where rt.resource_id is not null and rt.resourcestatus = 8 AND  CONVERT(Date, rt.startedat) >= '02.02.2015' AND  CONVERT(Date, rt.finishedat) < DateAdd(day,1,CONVERT ( datetime , '08.05.2015', 120 ))           
...
) outOfInterval on outOfInterval.inter = zr.interval

left join (select case when CONVERT(time, rt.startedat, 8) >= '00:00' and CONVERT(time, rt.finishedat , 8) <= '00:30'  then '00:00 - 00:29' end as inter, SUM(DATEDIFF(minute, rt.STARTEDAT, rt.FINISHEDAT)) as waittime from T_resourcetracking rt where rt.resource_id is not null and rt.resourcestatus = 8 AND  CONVERT(Date, rt.startedat) >= '02.02.2015' AND  CONVERT(Date, rt.finishedat) <= DateAdd(day,1,CONVERT ( datetime , '08.05.2015', 120 )) group by rt.startedat, rt.finishedat        
...
) inInterval on inInterval.inter = zr.interval

left join (select case when CONVERT(time, rt.startedat, 8) >= '00:00' and CONVERT(time, rt.startedat, 8) < '00:30'and CONVERT(time, rt.finishedat , 8) >= '00:30'  then '00:00 - 00:29' end as inter, (30-DATEPART(minute, rt.STARTEDAT)) as waittime from T_resourcetracking rt where rt.resource_id is not null and rt.resourcestatus = 8 AND  CONVERT(Date, rt.startedat) >= '02.02.2015' AND  CONVERT(Date, rt.finishedat) <= DateAdd(day,1,CONVERT ( datetime , '08.05.2015', 120 )) group by rt.startedat, rt.finishedat       
...
) startInInterval on startInInterval.inter = zr.interval

left join (select case when CONVERT(time, rt.startedat, 8) >= '00:00' and CONVERT(time,rt.finishedat, 8) < '00:30'and CONVERT(time, rt.STARTEDAT , 8) < '00:00'  then '00:00 - 00:29' end as inter,  DATEPART(minute, rt.finishedat) as waittime from T_resourcetracking rt where rt.resource_id is not null and rt.resourcestatus = 8 AND  CONVERT(Date, rt.startedat) >= '02.02.2015' AND  CONVERT(Date, rt.finishedat) <= DateAdd(day,1,CONVERT ( datetime , '08.05.2015', 120 )) group by rt.startedat, rt.finishedat        
...
) finishInInterval on finishInInterval.inter = zr.interval
group by zr.interval, outOfInterval.waittime, inInterval.waittime, startInInterval.waittime, finishInInterval.waittime, zr.nr   

这是结果:

    nr | interval      | waitingMinutes
    1  | 00:00 - 00:29 | 2
    2  | 00:30 - 00:59 | 7
...
    24 | 11:30 - 11:59 | 8
    24 | 11:30 - 11:59 | 51
...

因此,如您所见,我的结果集中有多个区间。

你知道如何将小组加入到一个小组并总结会议记录吗?我真的受够了,各种聚合函数都不适合我。

提前致谢!

@EDIT:如果这还不够困难,我们需要第二个规范,我忘了解释:我们不想看到 48 time-intervals 期间的所有等待时间,而是特定范围内所有等待时间的总和date-interval.

假设我们想知道上个月的总分钟数。那么结果集应该是这样的:

    nr | interval      | waitingMinutes
    1  | 00:00 - 00:29 | 0
    2  | 00:30 - 00:59 | 0
...
    20 | 09:30 - 09:59 | 0
    21 | 10:00 - 10:29 | 8
    22 | 10:30 - 10:59 | 73
    23 | 11:00 - 11:29 | 20
...

上个月所有 time-intervals 的会议记录汇总。例如,从 11:00 - 11:29 开始,在过去 30 天内实体总共等待了 20 分钟(例如昨天 10 分钟和前一天 10 分钟)。

这太难了,我真的不知道了,我想这对 SQL 来说太多了...

有什么建议吗?

这个想法是用 TALLY 使用 CTE 生成所有 48 个间隔,并加入您的数据,以便 2 个间隔相交。如果任何顶点在其他顶点之间,它们相交:

    a-----------------b
c------------------------d

    a-----------------b
           c-----------------d

    a------------------b
           c----d

    a------------------b
c----------d 

最后的select只是分组,根据大小写正确计算

DECLARE @t TABLE
    (
      sd DATETIME ,
      ed DATETIME ,
      st INT
    )

INSERT  INTO @t
VALUES  ( '2015-03-19 10:31:56', '2015-03-19 10:42:56', 8 ),
        ( '2015-03-19 10:25:56', '2015-03-19 10:35:56', 8 ),
        ( '2015-03-19 10:31:56', '2015-03-19 11:10:56', 8 ),
        ( '2015-03-19 10:25:56', '2015-03-19 11:10:56', 8 );

WITH    cte
          AS ( SELECT   DATEADD(mi,
                                30 * ( -1
                                       + ROW_NUMBER() OVER ( ORDER BY ( SELECT
                                                              1
                                                              ) ) ),
                                CAST('00:00:00' AS TIME)) sp ,
                        DATEADD(mi,
                                -1 + 30
                                * ROW_NUMBER() OVER ( ORDER BY ( SELECT
                                                              1
                                                              ) ),
                                CAST('00:00:00' AS TIME)) ep
               FROM     ( VALUES ( 1), ( 1), ( 1), ( 1), ( 1), ( 1), ( 1),
                        ( 1) ) t1 ( n )
                                  CROSS JOIN ( VALUES ( 1), ( 1), ( 1), ( 1),
                        ( 1), ( 1) ) t2 ( n )
             )
    SELECT  sp, ep,
            SUM(CASE WHEN CAST(t.sd AS TIME) < c.sp
                      AND CAST (t.ed AS TIME) > c.ep THEN DATEDIFF(mi, sp, ep)
                 WHEN CAST(t.sd AS TIME) BETWEEN c.sp AND c.ep
                      AND CAST(t.ed AS TIME) BETWEEN c.sp AND c.ep
                 THEN DATEDIFF(mi, CAST(sd AS TIME), CAST(ed AS TIME))
                 WHEN CAST(t.sd AS TIME) BETWEEN c.sp AND c.ep
                 THEN DATEDIFF(mi, CAST(sd AS TIME), ep)
                 ELSE DATEDIFF(mi, sp, CAST(ed AS TIME))
            END) AS Mi
    FROM    cte c
            JOIN @t t ON CAST(t.sd AS TIME) BETWEEN c.sp AND c.ep
                         OR CAST(t.ed AS TIME) BETWEEN c.sp AND c.ep
                         OR c.sp BETWEEN CAST(t.sd AS TIME) AND CAST(t.ed AS TIME)
                         OR c.ep BETWEEN CAST(t.sd AS TIME) AND CAST(t.ed AS TIME)

GROUP BY sp, ep

输出:

sp                  ep                  Mi
10:00:00.0000000    10:29:00.0000000    8
10:30:00.0000000    10:59:00.0000000    73
11:00:00.0000000    11:29:00.0000000    20

JOIN更改为LEFT JOIN以获得所有间隔。 您应该调整它以在 SUM 上使用 ISNULL 获得 0s。这也只考虑一天。

我会把你的问题分解成这样。我这里可能有一些因素略有偏差,但希望您能明白我要解决的问题。

我会用评论来分解脚本,但实际的事情应该是 运行 作为一个单一的查询:

declare @StartDate date
declare @EndDate date

select @StartDate = '20150202',@EndDate='20150508'

我已经将开始日期和结束日期作为参数打破了,因为我猜这些可能会发生变化,所以这给了我们一个地方来改变它们而不是很多

;With Dates as (
    select CAST(@StartDate as datetime) as Day
    union all
    select DATEADD(day,1,Day) from Dates where Day < @EndDate
)

第一个 CTE,Dates,生成感兴趣期间内的所有日期。如果您的数据库中有日历 table,则只需 select 代替

, PMNs as (
    select ROW_NUMBER() OVER (ORDER BY number)-1 as n
    from master..spt_values
)

下一个 CTE,PMNs 是我的 "poor man's numbers table" - 如果您的数据库中有实数 table,您可以用它代替

, DateTimes as (
    select
        n+1 as nr,
        DATEADD(minute,30*n,Day) as StartInclusive,
        DATEADD(minute,30*(n+1),Day) as EndExclusive
    from
        Dates d
            inner join
        PMNs p
            on
                p.n between 0 and 47
)

现在,真正有趣的是。我们结合前两个 CTE 生成 DateTimes - 所有感兴趣日期的所有半小时长周期的完整集合

select
    nr,
    CAST(time,StartInclusive) as StartTime,
    CAST(time,EndInclusive) as EndTime,
    SUM(
        DATEDIFF(minute,
            CASE WHEN dt.StartInclusive < rt.StartedAt THEN rt.StartedAt
                ELSE dt.StartInclusive END,
            CASE WHEN dt.EndExclusive > rt.finishedAt THEN rt.FinishedAt
                ELSE dt.EndExclusive END
        )) as TotalMinutes
from
    DateTimes dt
        inner join
    T_resourcetracking rt
        on
            dt.StartInclusive < rt.finishedAt and
            rt.startedAt < dt.EndExclusive
group by
    nr,
    CAST(time,StartInclusive),
    CAST(time,EndInclusive)

最后,我们将数据合并在一起。我们发现 resourceTracking 周期与我们的 DateTimes 周期之一重叠的位置(请注意 joinon 子句标识所有重叠)。然后在一些 CASE 表达式中进行一些操作,以计算出两个开始日期时间中的较晚日期时间和两个结束日期时间中较早的日期时间 - 这就是我们要减去的两个值。

如果您的 T_resourcetracking 也不是(与我的 DateTimes 一样)计算具有半开放时间间隔(包括开始时间,独占结束时间)的时间间隔,您可能需要进行一些调整所以看起来确实如此。

请尝试此解决方案。即使结束日期不是开始日期,您也可以使用它。

;with event_time as (
/*this is the input*/
select 1 id, convert(datetime,'2015-05-11 23:11') startdate, convert(datetime,'2015-05-12 00:15') finishdate
)
, event_time_convert as (
/*convert the input to calculation*/
select i.id, convert(time,i.startdate) startdate, DATEDIFF(MINUTE, i.startdate, i.finishdate) time_until_end
from event_time i
)
, intervall as (
/*create the intervall groups*/
select 1 id, CONVERT(time,'00:00') startdate, CONVERT(time,'00:29') finishdate
union all
select cs.id+1 id, DATEADD(minute,30,cs.startdate) startdate, DATEADD(minute,30,cs.finishdate) finishdate
from intervall cs
where cs.id<48
)
, event_time_in_intervall as (
/*calculate the waiting minutes in intervall*/
select i.id
    , cs.id intervall_id
    , case when DATEDIFF(minute,i.startdate, cs.finishdate) > i.time_until_end then i.time_until_end else DATEDIFF(minute,i.startdate, cs.finishdate) end time_in_intervall
    , case when DATEDIFF(minute,i.startdate, cs.finishdate) > i.time_until_end then null else DATEADD(minute,1,cs.finishdate) end new_startdate
    , case when DATEDIFF(minute,i.startdate, cs.finishdate) > i.time_until_end then 0 else i.time_until_end - DATEDIFF(minute,i.startdate, cs.finishdate)+1 end new_time_until_end
from event_time_convert i
  join intervall cs on i.startdate between cs.startdate and cs.finishdate /*this is the first intervall*/
union all
select i.id
  , cs.id intervall_id
  , case when DATEDIFF(minute,i.new_startdate, cs.finishdate) > i.new_time_until_end then i.new_time_until_end else DATEDIFF(minute,i.new_startdate, cs.finishdate)+1 end time_in_intervall
  , case when DATEDIFF(minute,i.new_startdate, cs.finishdate) > i.new_time_until_end then null else DATEADD(minute,1,cs.finishdate) end new_startdate
  , case when DATEDIFF(minute,i.new_startdate, cs.finishdate) > i.new_time_until_end then 0 else i.new_time_until_end - DATEDIFF(minute,i.new_startdate, cs.finishdate)+1 end new_time_until_end
from event_time_in_intervall i
  join intervall cs on i.new_startdate between cs.startdate and cs.finishdate
where i.new_time_until_end>0 /*if there is remaining time, I calculate with a recursion*/
)
/*the result*/
select i.id, CONVERT(varchar(5),i.startdate) + ' - ' + CONVERT(varchar(5), i.finishdate) intervall, s.sum_time_in_intervall waitingMinutes
from (
  select i.intervall_id, SUM(i.time_in_intervall) sum_time_in_intervall
  from event_time_in_intervall i
  group by i.intervall_id
  ) s
  join intervall i on s.intervall_id = i.id