Get a new formatted table via aggregation and group

I'm having a big problem with a SQL Server query and I really don't know how to do it.

The goal is to get a table differentiated by different time intervals from 00:00 - 00:29

to 23:30 - 23:59

. In each of these intervals, I want to summarize the total minutes of the entities that were waiting at that time. This information can be obtained using starttime

and endtime

and the state of the object, which looks like this:

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

      

As you can see, such an object can have status 8 from one interval (10:00 - 10:30) to another (10:30 - 11:00).

So far, I have solved this by defining 4 groups of time intervals (finish and start are in an interval, start with an interval, but end, start with an interval, but end as they begin and end from an interval), these 4 groups are connected by time intervals ...

I would post the code here, but this is too much. My result looks like this. Here are the start of the various parts of the query:

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   

      

And this is the result:

    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
...

      

So, as you can see, I have more than one spacing in my result set.

Do you have an idea how to join groups to one and sum up the minutes? I really got through it, every aggregate function didn't work for me.

Thanks in advance!

@EDIT: If this was tricky, we'll need a second spec, which I forgot to explain: we don't want to see all of the wait times during 48 time intervals, but the SUM of all those that are in a specific date interval.

Let's say we want to know how this has been done in the last month. Then the resulting set should look like this:

    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
...

      

The minutes are summed up over all time intervals of the last month. So, for example, from 11:00 to 11:29 for the last 30 days, entities waited only 20 minutes (for example, yesterday 10 minutes and up to 10 minutes a day).

It's so hard that I really don't know what I think is too much for SQL ...

Any suggestions?

+3


source to share


3 answers


I would break your problem something like this. I may have several factors from here, but hopefully you can see where I am going with this.

I'll unravel the script with a comment, but the actual thing should be run as one request:

declare @StartDate date
declare @EndDate date

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

      

I have split the start and end dates as parameters, as I assume they can be changed and so this gives us one place to change them, not many.

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

      

The first CTE,, Dates

generates all dates in the period of interest. If you have a calendar table in your database, just select it instead

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

      



The next CTE PMNs

is my "poor man's number table" - if you have a table of real numbers in your database, you can replace

, 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
)

      

Now, real hilarious. We combine the first two CTEs to generate DateTimes

a complete set of all one and a half hour periods on all dates of interest

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)

      

Finally, we will merge the data together. We find where a period resourceTracking

overlaps one of our periods DateTimes

(note that the sentence on

for join

identifies all overlaps). And then a little manipulation inside some expressions CASE

to work out the last of the two start dates and the early two days of the datetime are the two values ​​that we want to subtract.

If yours is T_resourcetracking

not (like mine DateTimes

) computational intervals with a half-open interval (including start time, exclusive end time), you probably want to make some adjustments to make it look like it.

+2


source


The idea creates all 48 bins with TALLY

s CTE

and connects with your data to cross 2 bins. They intersect if any of the vertices is between other vertices:

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

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

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

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

      

The last choice is just the grouping and the correct calculation depending on the case.

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

      



Output:

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

      

Change JOIN

to LEFT JOIN

to get all intervals. You have to configure it to get 0

help ISNULL

on SUM

. This also applies to only one day.

+1


source


Try this solution. You can use it even if the end date is on a different day than the start date.

;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

      

0


source







All Articles