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?
source to share
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.
source to share
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.
source to share
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
source to share