Finding business minutes between two dates - snowflake-cloud-data-platform

I am working on finding a solution of date difference down to the minute for business hours. I have a table that provides every date and a 1/0 column next to it as a business day. Additionally, I know (not in table) the working hours is 8:00 to 16:00. What I am having issues with is pulling in this column that has a 1 for business days and then adding in those hours and finding true date differences to the minute. On top of that, I am trying to find a solution of off hours time stamps. Ideas behind lets say 7:32:00 on 6/20/22 and 17:42:00 on 6/24/22 with a work holiday on 6/23/22. This is all done in snowflake for now.

I think this SQL should solve the problem.
In the CTE you need to put in the timestamp-columns, which you want to use as the borders for your business-minutes calculation. It will then calculate how many business dates are between the two border timestamps.
In the second query it will first calculate the difference between the timestamps if they are not on the same date. Afterwards there is another CASE block that will handle the cases, if the timestamps are on the same date.
CREATE OR REPLACE TABLE TEST.BUSINESS_DATES
AS
SELECT TO_DATE('2022-12-29') AS REF_DATE, 1 AS BUSINESS_DATE_INDICATOR
UNION ALL
SELECT TO_DATE('2022-12-30') AS REF_DATE, 1 AS BUSINESS_DATE_INDICATOR
UNION ALL
SELECT TO_DATE('2022-12-31') AS REF_DATE, 0 AS BUSINESS_DATE_INDICATOR
UNION ALL
SELECT TO_DATE('2023-01-01') AS REF_DATE, 0 AS BUSINESS_DATE_INDICATOR
UNION ALL
SELECT TO_DATE('2023-01-02') AS REF_DATE, 1 AS BUSINESS_DATE_INDICATOR
UNION ALL
SELECT TO_DATE('2023-01-03') AS REF_DATE, 0 AS BUSINESS_DATE_INDICATOR
UNION ALL
SELECT TO_DATE('2023-01-04') AS REF_DATE, 1 AS BUSINESS_DATE_INDICATOR
;
WITH PRE_INTERVALS AS
(SELECT
SUM(BUSINESS_DATE_INDICATOR) AS NUMBER_BUSINESS_DATES,
TO_TIMESTAMP_NTZ('2022-12-30 13:44:00') AS START_TS,
TO_TIMESTAMP_NTZ('2023-01-04 17:33:00') AS END_TS
FROM TEST.BUSINESS_DATES
WHERE REF_DATE BETWEEN TO_DATE(START_TS) AND TO_DATE(END_TS))
SELECT
CASE
WHEN TIME(START_TS) BETWEEN TO_TIME('08:00:00') AND TO_TIME('16:00:00')
THEN DATEDIFF('minutes', TO_TIME(START_TS), TO_TIME('16:00:00'))
WHEN TIME(START_TS) < TO_TIME('08:00:00')
THEN 480
WHEN TIME(START_TS) > TO_TIME('16:00:00')
THEN 0
END AS FIRST_DAY_MINUTES,
CASE
WHEN TIME(END_TS) BETWEEN TO_TIME('08:00:00') AND TO_TIME('16:00:00')
THEN DATEDIFF('minutes', TO_TIME('08:00:00'), TO_TIME(END_TS))
WHEN TIME(END_TS) < TO_TIME('08:00:00')
THEN 0
WHEN TIME(END_TS) > TO_TIME('16:00:00')
THEN 480
END AS LAST_DAY_MINUTES,
CASE
WHEN TO_DATE(START_TS) = TO_DATE(END_TS)
THEN
CASE
WHEN NUMBER_BUSINESS_DATES = 0
THEN 0
WHEN TIME(START_TS) BETWEEN TO_TIME('08:00:00') AND TO_TIME('16:00:00') AND TIME(END_TS) BETWEEN TO_TIME('08:00:00') AND TO_TIME('16:00:00')
THEN DATEDIFF('minutes', TO_TIME(START_TS), TO_TIME(END_TS))
WHEN TIME(START_TS) < TO_TIME('08:00:00') AND TIME(END_TS) > TO_TIME('16:00:00')
THEN 480
WHEN TIME(START_TS) < TO_TIME('08:00:00')
THEN DATEDIFF('minutes', TO_TIME('08:00:00'), TO_TIME(END_TS))
WHEN TIME(END_TS) > TO_TIME('16:00:00')
THEN DATEDIFF('minutes', TO_TIME(START_TS), TO_TIME('16:00:00'))
END
ELSE
(NUMBER_BUSINESS_DATES - 2) * 480 + FIRST_DAY_MINUTES + LAST_DAY_MINUTES
END AS DIFF_BUSINESS_MINUTES
FROM PRE_INTERVALS;
Best regards,
TK

Related

Calculate downtime

I would like to calculate the downtime for some processes.
My data could look like this:
Proces ID StartTime EndTime
A 1 24-07-2018 00:00:00 24-07-2018 00:02:54
A 2 24-07-2018 00:00:16 24-07-2018 00:02:55
A 3 24-07-2018 11:12:42 24-07-2018 11:15:10
A 4 24-07-2018 00:00:16 24-07-2018 00:02:55
In this example, ID 1, 2 and 4 are overlapping, but the downtime should only be from 00.00.00 to 00.02.55 plus the downtime for ID 3.
I am not sure how to compare all the times and only getting it to use the overlapping time once.
If it is unclear, then ask!
I hope someone can help me.
i think is better that handle this business Out of TSQL , For example in your Application you can get each day and use a bitarray for each minute and calculate minimum and maximum Time in each overlapping range .
this is very complex in tsql and i thinks every solution has Performance ISSUE.
Could be solved using self-join as follows
select t.process, sum(datediff(second, t.StartTime, t.EndTime))
from
(
select distinct d1.process, min(d2.StartTime) StartTime, max(d2.EndTime) EndTime
from data d1
left join data d2 on d2.EndTime > d1.StartTime and d2.StartTime < d1.EndTime
group by d1.process, d1.id
) t
group by t.process
DBFiddle DEMO
However, the performance for large data can be quite poor. At least indexes on (process, id, endtime) and (process, id, starttime) should be available.
Could you please try following SQL query with more data
Please try to create sample data for different processes as well
This query sums downtime grouped by process, you can remove process from aggregation SELECT statement (which is the last query) to calculate overall downtime. Or even add GroupId to the list for downtimes per chains of overlapping downtime periods
Please have a look at SQL tutorial on SQL Queries for Overlapping Time Periods which explains the solution in detail
;with rawdata as (
select
Process, id, StartTime, EndTime,
ROW_NUMBER() over (partition by Process order by StartTime, EndTime) as rn
from Processes
), cte as (
select
Process, StartTime, EndTime, rn, 1 as GroupId
from rawdata
where rn = 1
union all
select
p1.Process,
case
when (p1.starttime between p2.starttime and p2.endtime) then p2.starttime
when (p2.starttime between p1.starttime and p1.endtime) then p1.starttime
when (p1.starttime < p2.starttime and p1.endtime > p2.endtime) then p1.starttime
when (p1.starttime > p2.starttime and p1.endtime < p2.endtime) then p2.starttime
else p2.starttime
end as StartTime,
case
when (p1.EndTime between p2.starttime and p2.endtime) then p2.EndTime
when (p2.endtime between p1.starttime and p1.endtime) then p1.endtime
when (p1.starttime < p2.starttime and p1.endtime > p2.endtime) then p1.endtime
when (p1.starttime > p2.starttime and p1.endtime < p2.endtime) then p2.endtime
else p2.endtime
end as EndTime,
p2.rn,
case when
(p1.starttime between p2.starttime and p2.endtime) or
(p1.endtime between p2.starttime and p2.endtime) or
(p1.starttime < p2.starttime and p1.endtime > p2.endtime) or
(p1.starttime > p2.starttime and p1.endtime < p2.endtime)
then
p1.GroupId
else
(p1.GroupId+1)
end as GroupId
from cte p1
inner join rawdata p2
on p1.Process = p2.Process and
(p1.rn+1) = p2.rn
)
select
Process,
sum(datediff(second, StartTime, EndTime)) totalDownTime
from (
select
Process, GroupId, min(StartTime) StartTime, max(EndTime) EndTime
from cte
group by Process, GroupId
) t
group by Process
Output is as follows
Hoping to be useful,

Chained records - counting repetition of records

I have records about customer calls like;
PHONENO CALLTIME REP
======== =================== ===
01555444 10.03.2017 10:30:00 N <- first occurence of 01555444
02888999 12.03.2017 11:40:20 N
01555444 15.03.2017 18:22:33 Y <- repeated 1st time 01555444
03666777 18.03.2017 20:36:44 N
01555444 19.03.2017 08:15:47 Y <- repeated 2nd time 01555444
01555444 30.03.2017 22:18:30 N <- first occurence of 01555444 (gap more than 10 days)
If a call occures within next 10 from previous call (from the same phone number), then it is assumed a repeated call (assigned 'Y' in column REP).
Now I want to have the table like this with number of repetitions:
PHONENO CALLTIME REP REPNO
======== =================== === =====
01555444 10.03.2017 10:30:00 N 0
02888999 12.03.2017 11:40:20 N 0
01555444 15.03.2017 18:22:33 Y 1
03666777 18.03.2017 20:36:44 N 0
01555444 19.03.2017 08:15:47 Y 2
01555444 30.03.2017 22:18:30 N 0
REPNO represents the number of (chained) call repetition (within 10 days).
How to calculate this?
Here's a way of doing it that uses the tabibitosan method to identify the groups of repeated rows:
WITH cust_calls AS (SELECT '01555444' phoneno, to_date('10/03/2017 10:30:00', 'dd/mm/yyyy hh24:mi:ss') calltime FROM dual UNION ALL
SELECT '02888999' phoneno, to_date('12/03/2017 11:40:20', 'dd/mm/yyyy hh24:mi:ss') calltime FROM dual UNION ALL
SELECT '01555444' phoneno, to_date('15/03/2017 18:22:33', 'dd/mm/yyyy hh24:mi:ss') calltime FROM dual UNION ALL
SELECT '03666777' phoneno, to_date('18/03/2017 20:36:44', 'dd/mm/yyyy hh24:mi:ss') calltime FROM dual UNION ALL
SELECT '01555444' phoneno, to_date('19/03/2017 08:15:47', 'dd/mm/yyyy hh24:mi:ss') calltime FROM dual UNION ALL
SELECT '01555444' phoneno, to_date('30/03/2017 22:18:30', 'dd/mm/yyyy hh24:mi:ss') calltime FROM dual UNION ALL
SELECT '01555444' phoneno, to_date('30/04/2017 23:42:31', 'dd/mm/yyyy hh24:mi:ss') calltime FROM dual UNION ALL
SELECT '01555444' phoneno, to_date('05/05/2017 16:35:41', 'dd/mm/yyyy hh24:mi:ss') calltime FROM dual UNION ALL
SELECT '01555444' phoneno, to_date('20/05/2017 21:20:52', 'dd/mm/yyyy hh24:mi:ss') calltime FROM dual UNION ALL
SELECT '02888999' phoneno, to_date('12/03/2017 11:45:20', 'dd/mm/yyyy hh24:mi:ss') calltime FROM dual),
-- end of mimicking a table with your sample data in it. You do not need the above subquery, since you already have the table.
initial_info AS (SELECT phoneno,
calltime,
CASE WHEN calltime - LAG(calltime) OVER (PARTITION BY phoneno ORDER BY calltime) <= 10 THEN 'Y' ELSE 'N' END rep_row
FROM cust_calls),
middle_info AS (SELECT phoneno,
calltime,
rep_row rep,
CASE WHEN rep_row = 'Y' THEN
row_number() OVER (PARTITION BY phoneno ORDER BY calltime)
- row_number() OVER (PARTITION BY phoneno, rep_row ORDER BY calltime)
END rep_grp
FROM initial_info)
SELECT phoneno,
calltime,
rep,
CASE WHEN rep_grp is not NULL THEN
row_number() OVER (PARTITION BY phoneno, rep_grp ORDER BY calltime)
END repno
FROM middle_info
ORDER BY phoneno, calltime;
PHONENO CALLTIME REP REPNO
-------- ------------------- --- ----------
01555444 05/05/2017 16:35:41 Y 1
01555444 10/03/2017 10:30:00 N
01555444 15/03/2017 18:22:33 Y 1
01555444 19/03/2017 08:15:47 Y 2
01555444 20/05/2017 21:20:52 N
01555444 30/03/2017 22:18:30 N
01555444 30/04/2017 23:42:31 N
02888999 12/03/2017 11:40:20 N
02888999 12/03/2017 11:45:20 Y 1
03666777 18/03/2017 20:36:44 N
This works by first identifying the repeated rows by comparing the current row's calltime with the previous row's calltime and deciding if it's within 10 days or not. If you already have this info, you can skip this step and go straight to the next.
Next, we use the tabibitosan method to compare consecutive rows over all rows for the same phoneno and over all rows where rep_row is 'Y'.
Then we can use the number output by the previous step to partition the phoneno rows up even further, and then apply the row_number() analytic function to it.

reset window function when the time gap is over one hour

I have a dataset already sorted by a window function in sql:
ROW_NUMBER() OVER (PARTITION BY LOAN_NUMBER, CAST(CREATED_DATE AS DATE) ORDER BY LOAN_NUMBER, CREATED_DATE) AS ROW_IDX
shown as above. I wonder if there's a way that reset the ROW_IDX when the CREATED_DATE has begun to have a value with over one hour gap to the minimum datetime in a specific day.
For example, the row index for row 3 should be 1 because the time gap between 2016-11-03 15:39:16.000 and 2016-11-03 12:44:11.000 is over one hour.And row index of row 4 will be 2.
I've tried several ways to manipulate the datatime column, since the consideration is about 'gap' instead of moments of the day, no rounding methods worked perfectly.
Are mean ,when the gap more than 60 minutes, will restart at 1?
Which version are you use? If it is SQL Server 2012+, you can try this.
The following query is not satisfying, but wish can give you help.
Calculating the diff minutes between continuous two line.
Check the diff minutes whether greater than one hour
Get row number base on the gap time has same situation continuously.
Sorry if I can not describe clear. My english is not well.
;WITH tb(RptDate,ISSUE_ID,ACCOUNT,CREATED_DATE )AS(
select '2017-01-17','35775','76505156','2016-11-03 12:44:11.000' UNION
select '2017-01-17','35793','76505156','2016-11-03 12:51:43.000' UNION
-- select '2017-01-17','35793','76505156','2016-11-03 13:47:43.000' UNION
-- select '2017-01-17','35793','76505156','2016-11-03 14:45:43.000' UNION
select '2017-01-17','36097','76505156','2016-11-03 15:39:16.000' UNION
select '2017-01-17','36132','76505156','2016-11-03 15:52:51.000' UNION
select '2017-01-17','41391','76505156','2016-11-10 10:49:30.000'
)
SELECT *,ROW_NUMBER()OVER(PARTITION BY tt.ACCOUNT,a ORDER BY tt.ACCOUNT, rn) AS ROW_IDX FROM (
SELECT * ,rn-ROW_NUMBER () OVER (PARTITION BY ACCOUNT, CAST(CREATED_DATE AS DATE),n ORDER BY rn) AS a
FROM (
SELECT *, ROW_NUMBER()OVER(PARTITION BY ACCOUNT ORDER BY CREATED_DATE) AS rn
,CASE WHEN DATEDIFF(MINUTE, LAG(CREATED_DATE)OVER(PARTITION BY ACCOUNT ORDER BY CREATED_DATE),tb.CREATED_DATE)>60 THEN 1 ELSE 0 END AS n
,ISNULL(DATEDIFF(MINUTE, LAG(CREATED_DATE)OVER(PARTITION BY ACCOUNT ORDER BY CREATED_DATE),tb.CREATED_DATE),0) AS DiffMin
FROM tb
) AS t
) AS tt
ORDER BY rn
RptDate ISSUE_ID ACCOUNT CREATED_DATE rn n DiffMin a ROW_IDX
---------- -------- -------- ----------------------- -------------------- ----------- ----------- -------------------- --------------------
2017-01-17 35775 76505156 2016-11-03 12:44:11.000 1 0 0 0 1
2017-01-17 35793 76505156 2016-11-03 12:51:43.000 2 0 7 0 2
2017-01-17 36097 76505156 2016-11-03 15:39:16.000 3 1 168 2 1
2017-01-17 36132 76505156 2016-11-03 15:52:51.000 4 0 13 1 1
2017-01-17 41391 76505156 2016-11-10 10:49:30.000 5 1 9777 4 1
It is another script,Do not use the LAG function, Each step has a statement:
;WITH tb(RptDate,ISSUE_ID,ACCOUNT,CREATED_DATE )AS(
select '2017-01-17','35775','76505156','2016-11-03 12:44:11.000' UNION
select '2017-01-17','35793','76505156','2016-11-03 12:51:43.000' UNION
-- select '2017-01-17','35793','76505156','2016-11-03 13:47:43.000' UNION
-- select '2017-01-17','35793','76505156','2016-11-03 14:45:43.000' UNION
select '2017-01-17','36097','76505156','2016-11-03 15:39:16.000' UNION
select '2017-01-17','36132','76505156','2016-11-03 15:52:51.000' UNION
select '2017-01-17','41391','76505156','2016-11-10 10:49:30.000'
),t1 AS(
SELECT *, ROW_NUMBER()OVER(PARTITION BY ACCOUNT ORDER BY CREATED_DATE) AS rn FROM tb
),t2 AS (
SELECT t1.*,CASE WHEN DATEDIFF(MINUTE,tt.CREATED_DATE,t1.CREATED_DATE)>60 THEN 1 ELSE 0 END AS m
,t1.rn-ROW_NUMBER()OVER(PARTITION BY t1.ACCOUNT,CASE WHEN DATEDIFF(MINUTE,tt.CREATED_DATE,t1.CREATED_DATE)>60 THEN 1 ELSE 0 END ORDER BY t1.CREATED_DATE) AS a
FROM t1 LEFT JOIN t1 AS tt ON tt.ACCOUNT=t1.ACCOUNT AND tt.rn=t1.rn-1
),t3 AS(
SELECT *,ROW_NUMBER()OVER(PARTITION BY ACCOUNT,t2.a ORDER BY CREATED_DATE) AS ROW_IDX
FROM t2
)
SELECT * FROM t3
ORDER BY t3.ACCOUNT,t3.CREATED_DATE

SQL, using Group by until specific trend (increment, decrement, same)

I would like to know how can i modify my code for considering all the same values of suppose 10 as UP till the time it is incrementing and then down for decrement and SAME if there is no change till the time there is no variation in the value (increment, decrement, same).
Here is my code :
;with etape1 as
(
select ROW_NUMBER() OVER(ORDER BY mnth) AS id,* from [InsideTSQL2008].[alioune].[Sales]
)
,
etape2 as
(
select
a.id, b.mnth AS START , a.mnth AS FINISH ,
a.qty - b.qty AS TREND
FROM
etape1 a
LEFT JOIN etape1 b
on a.id = b.id+1
)
select * from etape2;
My Result is :
id START FINISH TREND
1 NULL 2007-12-01 NULL
2 2007-12-01 2008-01-01 10
3 2008-01-01 2008-02-01 10
4 2008-02-01 2008-03-01 10
5 2008-03-01 2008-04-01 10
6 2008-04-01 2008-05-01 0
7 2008-05-01 2008-06-01 -10
8 2008-06-01 2008-07-01 -10
9 2008-07-01 2008-08-01 -10
10 2008-08-01 2008-09-01 -10
11 2008-09-01 2008-10-01 10
12 2008-10-01 2008-11-01 -10
13 2008-11-01 2008-12-01 20
14 2008-12-01 2009-01-01 10
15 2009-01-01 2009-02-01 10
16 2009-02-01 2009-03-01 -40
My final result as required should be like :
Start End Trend
200712 200712 unknown
200801 200804 UP
200805 200805 SAME
200806 200809 DOWN
200810 200810 UP
200811 200811 DOWN
200812 200812 UP
200903 200903 DOWN
200904 200905 SAME
200906 200907 UP
Any help would be really helpful; Thanks
Took me a few goes (and a few hours), but I think I have what you want:
DECLARE #Sales AS TABLE (mnth datetime, qty int)
INSERT INTO #Sales
SELECT '2016-01-01', 10 UNION ALL
SELECT '2016-02-01', 20 UNION ALL
SELECT '2016-03-01', 30 UNION ALL
SELECT '2016-04-01', 40 UNION ALL
SELECT '2016-05-01', 40 UNION ALL
SELECT '2016-06-01', 30 UNION ALL
SELECT '2016-07-01', 20 UNION ALL
SELECT '2016-08-01', 30 UNION ALL
SELECT '2016-09-01', 40 UNION ALL
SELECT '2016-10-01', 45 UNION ALL
SELECT '2016-11-01', 50
;WITH etape1 AS (
SELECT ROW_NUMBER() OVER(ORDER BY mnth) AS id, * FROM #Sales
)
, etape2 AS (
SELECT id, lag(mnth) OVER (ORDER BY id) AS START, mnth AS FINISH, CASE WHEN qty - LAG(qty) OVER (ORDER BY id) < 0 THEN -1 WHEN qty - LAG(qty) OVER (ORDER BY id) > 0 THEN 1 ELSE 0 END AS TREND
FROM etape1
)
, etape3 AS (
SELECT id, START, FINISH, TREND, lag(TREND) OVER (ORDER BY id) AS PrevTrend
FROM etape2
)
, etape4 AS (
SELECT id, START, FINISH, TREND, SUM(CASE WHEN TREND = PREVTREND THEN 0 ELSE 1 END) OVER (ORDER BY id ROWS UNBOUNDED PRECEDING) AS Change
FROM etape3
)
SELECT MIN(START) AS START, MAX(FINISH) AS FINISH, CASE WHEN MIN(TREND) IS NULL THEN 'Unknown' WHEN MIN(TREND) < 0 THEN 'Down' WHEN MIN(TREND) > 0 THEN 'Up' WHEN MIN(Start) is NULL THEN 'Unknown' ELSE 'Same' END AS TREND
FROM etape4
GROUP BY Change
ORDER BY START
Results are:
START FINISH TREND
NULL 2016-01-01 Unknown
2016-01-01 2016-04-01 Up
2016-04-01 2016-05-01 Same
2016-05-01 2016-07-01 Down
2016-07-01 2016-11-01 Up

Receive a new formatted table via aggregation and group by

I am having a big issue with a SQL Server query here and I really don't know how to go on with it.
The aim is to receive a table differentiated by different time-intervals going from 00:00 - 00:29 to 23:30 - 23:59. In each of these intervals I want to sum up the total minutes of entities which waited during these times. This information can be received by a starttime, and endtime and the status of the entity, 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 see such an entity can have the status 8 from one interval (10:00 - 10:30) into another (10:30 - 11:00).
Until now I solved this by defining 4 groups of time-intervals (finish and start are both in interval, start out of interval but finish in, start in interval but finish out, both start and finish out of interval) these 4 groups are joined by the time-intervals.
I would post the code here but it is too much. My result looks like this. Here are the beginnings of the different 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 see I have more then one of an interval in my result set.
Do you have any idea how to join the groups to one and sum the minutes up? I am really done with it, every kind of aggregate function did not work for me.
Thanks in advance!
#EDIT: If this was not difficult enough we need a second specification which I forgot to explain: We do not want to see all waitingtimes during the 48 time-intervals but the SUM of all those within a specific date-interval.
Let's say we want to know the summed up minutes from the last month. Then the result set should look like:
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 - 11:29 in the last 30 days the entities waited 20 minutes in total (e.g. yesterday 10 minutes and the day before 10 minutes).
This is so difficult that I have really no clue anymore thinking that this is too much for SQL...
Any suggestions?
I would break you problem down something like this. I may have a few factors slightly off here but hopefully you can see where I'm going with this.
I'll break up the script with commentary, but the actual thing should be run as one single query:
declare #StartDate date
declare #EndDate date
select #StartDate = '20150202',#EndDate='20150508'
I've broken the start and end dates out as parameters as I guess these are subject to change and so this gives us one place to change them rather than many
;With Dates as (
select CAST(#StartDate as datetime) as Day
union all
select DATEADD(day,1,Day) from Dates where Day < #EndDate
)
First CTE, Dates, generates all dates within the period of interest. If you have a calendar table in your database, just select from it instead
, PMNs as (
select ROW_NUMBER() OVER (ORDER BY number)-1 as n
from master..spt_values
)
Next CTE, PMNs is my "poor man's numbers table" - if you have a real numbers table in your database, you can substitute that instead
, 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, the real fun one. We combine the first two CTEs to generate DateTimes - the complete set of all half hour long periods across 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)
And finally, we combine the data together. We find where a resourceTracking period overlaps one of our DateTimes periods (note the on clause for the join identifies all overlaps). And then a little manipulation inside some CASE expressions to work out the latter of the two start datetimes and the earlier of the two end datetimes - those are the two values we want to subtract.
If your T_resourcetracking isn't also (as with my DateTimes) computing intervals with a semi-open interval (inclusive start time, exclusive end time) you probably want to make some adjustments so that it does seem to be.
The idea is producing all 48 intervals with TALLY using CTE and joining to your data so that 2 intervals intersect. They intersect if any of vertice is between other vertices:
a-----------------b
c------------------------d
a-----------------b
c-----------------d
a------------------b
c----d
a------------------b
c----------d
The last select is just grouping and correct calculation depending on 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 in order to get all intervals.
You should tweak this to get 0s using ISNULL on SUM. Also this considers only one day.
Please try this solution. You can use it even if the finishdate is on an other day than the startdate.
;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

Resources