how to calculate in sql only the minutes between 2 time slots - sql-server

I have to create a query in SQL Server that calculates the difference in minutes between the time of a start date and the time of an end date considering only the time slots between 8.30 - 13.00 and 15.00 - 19. So, if the date times start and end date include time slots outside the valid range, these minutes must not be included in the calculation.
I use:
select datediff(minute, startdate, enddate) as minutes
from table
But I can't exclude invalid minutes.
Here are some examples (with datetime values).
StardDate: 2022-12-10 00:00:00.000
EndDate: 2022-12-10 09:00:15.000
Duration: 30 minutes.
StartDate: 2022-12-10 10:00:30.000
EndDate: 2022-12-10 16:00:00.000
Duration: 180 (from 10.00 to 13.00) + 60 (from 15.00 to 16.00) minutes.
StartDate: 2022-12-10 00:00:00.000
EndDate: 2022-12-10 08:30:00.000
Duration: 0 minutes.
StartDate: 2022-12-10 00:08:00.000
EndDate: 2022-12-11 03:00:00.000
Duration: 510 minutes.

Assuming you have a Calendar table, one method would be to use that table to create a row for each time slot, and then JOIN to that. Then you can use a CASE expression to use the appropriate start/end time for the slot, and finally aggregate:
SELECT *
INTO dbo.YourTable
FROM (VALUES(1,CONVERT(datetime2(0),'20221210 00:00:00'),CONVERT(datetime2(0),'20221210 09:00:15')),
(2,CONVERT(datetime2(0),'20221210 00:08:00'),CONVERT(datetime2(0),'20221211 03:00:00')))V(SomeID,StartDate,EndDate);
GO
SELECT YT.SomeID,
SUM(DATEDIFF(MINUTE,V.StartDate, V.EndDAte)) AS TotalMinutes
FROM dbo.YourTable YT
LEFT JOIN (SELECT DATEADD(MINUTE,V.StartMinutes, CONVERT(datetime2(0),CT.CalendarDate)) AS StartDate,
DATEADD(MINUTE,V.EndMinutes, CONVERT(datetime2(0),CT.CalendarDate)) AS EndDate
FROM dbo.CalendarTable CT
CROSS APPLY (VALUES(510,780),
(900,1140))V(StartMinutes,EndMinutes)) CT ON YT.StartDate <= CT.EndDate
AND YT.EndDate >= CT.StartDate
CROSS APPLY (VALUES(CASE WHEN YT.StartDate > CT.StartDate THEN YT.StartDate ELSE CT.StartDate END, CASE WHEN YT.EndDate < CT.EndDate THEN YT.EndDate ELSE CT.EndDate END))V(StartDate,EndDate)
GROUP BY YT.SomeID;
GO
DROP TABLE dbo.YourTable;

Related

How to calculate periodIds using SQL server

I have a table1 which stores the data for multiple dates say (between 2022-01-01 to 2022-01-10) and I have another table2 which has time interval and has every 5mins information of dates available in table1 (i.e. 288 records for each day in above table).
Now, How I can write a query for table 2 to calculate time interval for each specific date in table 1. Say I need time intervals between
2022-01-01 12:00:00 to 2022-01-01 02:00:00
2022-01-02 00:05:00 to 2022-01-02 23:00:00
2022-01-04 00:05:00 to 2022-01-10 15:00:00
I tried using DATEDIFF function but that is not giving the results. let's say If I take date 2022-01-02 00:00:00 then my time interval should go back to 1 it should be 2 for 2022-01-02 00:05:00
Below is the example of data:
Table 1:
ID Start date End date
20030917.D0001 2003-09-17 14:10:00 2003-09-18 14:20:00
Table 2:
Date Time interval Amount
2003-09-17 1 150
2003-09-17 2 100
2003-09-17 3 200
2003-09-17 288 250
2003-09-18 1 250
2003-09-18 2 300
2003-09-18 3 1100
2003-09-18 288 150
The time interval in table 2 is every 5 mins of that particular date. Now I need to fetch the data from table 2 which matches with specific date and time in table1
i'm guessing here, if you are looking to return data from table2 with specific time and the table formatted like below
table1
tbl1id
Date
1
2022-01-01
2
2022-01-10
table2
tbl2id
tbl1id
Time
1
1
12:00:00
2
1
12:05:00
3
1
12:10:00
your query should be
select *
from table2
where Time between '12:00:00' and '02:00:00'
and tbl1id
in
(
select tbl1id
from table1
where Date = '2022-01-01'
)
however it you are looking for just looking for calculate time interval in Minute
DECLARE #startdate DATETIME2 = '2022-01-02 00:00:00';
DECLARE #enddate DATETIME2 = '2022-01-02 00:05:00';
SELECT DATEDIFF(Minute, #startdate, #enddate);
that should do it.

Snowflake- Calculate day of Quarter

Snowflake has the simply function Quarter(timestamp()) which returns current quarter, but wondering how to do day of QTR , all tutorials reference Postgres/ sql server.
Goal - create a date table, and show what day of the quarter it is for the next 20 years.
SELECT column1::timestamp as d,
DATE_TRUNC('QUARTER',d) as q,
DATEDIFF('day',q, d) as doq
FROM VALUES ('2019-10-30'),('2019-10-01');
gives 0 for the first day of the quarter, so if you need that to be 1 you can +1 that datadiff.
D Q DOQ
2019-10-30 00:00:00.000 2019-10-01 00:00:00.000 29
2019-10-01 00:00:00.000 2019-10-01 00:00:00.000 0
[Edit:] After re-reading your goal of a 20 year table, here is some code I have used in snowflake in the past to just that:
CREATE OR REPLACE TABLE twenty_years_of_days(date) AS
SELECT DATEADD(day, rn, CURRENT_DATE) as date,
DATE_TRUNC('QUARTER',date) as quarter,
DATEDIFF('day',quarter, date) as doq
FROM (
SELECT row_number() over(order by 1) as rn
FROM TABLE(GENERATOR(rowCount => 365*20)) v
);

Populating a list of dates without a defined end date - SQL server

I have a list of accounts and their cost which changes every few days.
In this list I only have the start date every time the cost updates to a new one, but no column for the end date.
Meaning, I need to populate a list of dates when the end date for a specific account and cost, should be deduced as the start date of the same account with a new cost.
More or less like that:
Account start date cost
one 1/1/2016 100$
two 1/1/2016 150$
one 4/1/2016 200$
two 3/1/2016 200$
And the result I need would be:
Account date cost
one 1/1/2016 100$
one 2/1/2016 100$
one 3/1/2016 100$
one 4/1/2016 200$
two 1/1/2016 150$
two 2/1/2016 150$
two 3/1/2016 200$
For example, if the cost changed in the middle of the month, than the sample data will only hold two records (one per each unique combination of account-start date-cost), while the results will hold 30 records with the cost for each and every day of the month (15 for the first cost and 15 for the second one). The costs are a given, and no need to calculate them (inserted manually).
Note the result contains more records because the sample data shows only a start date and an updated cost for that account, as of that date. While the results show the cost for every day of the month.
Any ideas?
Solution is a bit long.
I added an extra date for test purposes:
DECLARE #t table(account varchar(10), startdate date, cost int)
INSERT #t
values
('one','1/1/2016',100),('two','1/1/2016',150),
('one','1/4/2016',200),('two','1/3/2016',200),
('two','1/6/2016',500) -- extra row
;WITH CTE as
( SELECT
row_number() over (partition by account order by startdate) rn,
*
FROM #t
),N(N)AS
(
SELECT 1 FROM(VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))M(N)
),
tally(N) AS -- tally is limited to 1000 days
(
SELECT ROW_NUMBER()OVER(ORDER BY N.N) - 1 FROM N,N a,N b
),GROUPED as
(
SELECT
cte.account, cte.startdate, cte.cost, cte2.cost cost2, cte2.startdate enddate
FROM CTE
JOIN CTE CTE2
ON CTE.account = CTE2.account
and CTE.rn = CTE2.rn - 1
)
-- used DISTINCT to avoid overlapping dates
SELECT DISTINCT
CASE WHEN datediff(d, startdate,enddate) = N THEN cost2 ELSE cost END cost,
dateadd(d, N, startdate) startdate,
account
FROM grouped
JOIN tally
ON datediff(d, startdate,enddate) >= N
Result:
cost startdate account
100 2016-01-01 one
100 2016-01-02 one
100 2016-01-03 one
150 2016-01-01 two
150 2016-01-02 two
200 2016-01-03 two
200 2016-01-04 one
200 2016-01-04 two
200 2016-01-05 two
500 2016-01-06 two
Thank you #t-clausen.dk!
It didn't solve the problem completely, but did direct me in the correct way.
Eventually I used the LEAD function to generate an end date for every cost per account, and then I was able to populate a list of dates based on that idea.
Here's how I generate the end dates:
DECLARE #t table(account varchar(10), startdate date, cost int)
INSERT #t
values
('one','1/1/2016',100),('two','1/1/2016',150),
('one','1/4/2016',200),('two','1/3/2016',200),
('two','1/6/2016',500)
select account
,[startdate]
,DATEADD(DAY, -1, LEAD([Startdate], 1,'2100-01-01') OVER (PARTITION BY account ORDER BY [Startdate] ASC)) AS enddate
,cost
from #t
It returned the expected result:
account startdate enddate cost
one 2016-01-01 2016-01-03 100
one 2016-01-04 2099-12-31 200
two 2016-01-01 2016-01-02 150
two 2016-01-03 2016-01-05 200
two 2016-01-06 2099-12-31 500
Please note that I set the end date of current costs to be some date in the far future which means (for me) that they are currently active.

TSQL - SQL 2008 - Across a date range (Start Date to End Date) is it possible to break this range into seperate rows by (and exluding) weekends?

I apologize for the poorly worded title, I have been given a task beyond my limited skillset and was hoping someone might help.
We have employees who have 24/7 schedules for booking work and field assignments (over weekends as well) but this does not apply to vacation. Because of this I have been tasked to break a single date range up across weekends (and exclude them)
For Example:
Start Date: 30/04/2015 End Date: 13/05/2015
30/04/2015, 01/05/2015
04/05/2015, 05/05/2015, 06/05/2015, 07/05/2015, 08/05/2015,
11/05/2015, 12/05/2015, 13/05/2015,
Note: The weekends have been excluded and the date range has been split into three across the three weeks.
Preferably: Include the start and end points for each range like so
30/04/2015 - 01/05/2015 --(the same as it is the start and end dates)
04/05/2015 - 08/05/2015
11/05/2015 - 13/05/2015
I have no idea if this is even possible due to my very limited knowledge and hope I have explained enough so that some kind soul could potentially see if such a thing is even possible.
The database application we use is TSQL on SQL 2008.
Many thanks.
Thanks for the fun problem. Note: I use the standard date format, but the concept is the same.
DECLARE #StartDate DATE = '20150430', --April 30, 2015
#EndDate DATE = '20150513'; --May 13,2015
WITH CTE_Dates
AS
(
SELECT #StartDate dates
UNION ALL
SELECT DATEADD(DAY,1,dates)
FROM CTE_Dates
WHERE dates < #EndDate
),
CTE_weeks
AS
(
SELECT dates,
DATEPART(WEEK,dates) WeekID
FROM CTE_Dates
WHERE DATENAME(WEEKDAY,dates) NOT IN ('Saturday','Sunday') --doesn't include weekends
)
SELECT WeekID,
MIN(dates) StartDate,
MAX(dates) EndDate,
STUFF(list_dates,1,1,'') list
FROM CTE_weeks A
CROSS APPLY (
SELECT ',' + CAST(dates AS VARCHAR(100))
FROM CTE_weeks B
WHERE A.WeekID = B.WeekID
ORDER BY dates
FOR XML PATH('')
) CA(list_dates)
GROUP BY WeekID,STUFF(list_dates,1,1,'')
Results:
WeekID StartDate EndDate list
----------- ---------- ---------- ------------------------------------------------------------
18 2015-04-30 2015-05-01 2015-04-30,2015-05-01
19 2015-05-04 2015-05-08 2015-05-04,2015-05-05,2015-05-06,2015-05-07,2015-05-08
20 2015-05-11 2015-05-13 2015-05-11,2015-05-12,2015-05-13
This seems to work. It assumes you give it a start date that's a weekday:
declare #StartDate datetime = '20150430'
declare #EndDate datetime = '20150513'
; With Ord as (
select #StartDate as StartAt,#StartDate as EndAt
union all
select StartAt,DATEADD(day,1,EndAt)
from Ord where DATEPART(weekday,EndAt) != DATEPART(weekday,'20150710') --Known Friday
and EndAt < #EndDate
union all
select DATEADD(day,3,EndAt),DATEADD(day,3,EndAt)
from Ord where DATEPART(weekday,EndAt) = DATEPART(weekday,'20150710') --Still known Friday
and DATEADD(day,3,EndAt) <= #EndDate
)
select StartAt,MAX(EndAt) as EndAt
from Ord
group by StartAt
Result:
StartAt EndAt
----------------------- -----------------------
2015-04-30 00:00:00.000 2015-05-01 00:00:00.000
2015-05-04 00:00:00.000 2015-05-08 00:00:00.000
2015-05-11 00:00:00.000 2015-05-13 00:00:00.000
I do my comparisons about DATEPART using a "known good" (i.e. I just randomly selected one from a calendar) Friday so that this code works for any DATEFIRST setting.

Is this the most efficient way of doing this TSQL calculation of datediff

I have a table of data similar to below where I need to calculate the sum of all the paused time up until today. The columns can have any date in them, so PauseStart can be a future date , and PauseEnd can also be a future date. A Null date (20991231) is considered open ended, i.e. no end date to the pause was selected.
NB : Dates are UK date format
The data
PauseID RecID PauseStart PauseEnd
1022 10 2013-01-04 15:52:04.320 2013-01-21 00:00:00.000
1023 10 2013-01-01 00:00:00.000 2013-01-02 00:00:00.000
1024 10 2013-01-05 00:00:00.000 2099-01-01 00:00:00.000
The data above shows that we had a pause between 1/1/2013 and 2/1/2013, a pause between 4/1/2013 and 21/1/2013 (which should register in the sum as 4/1/2013 to 7/1/2013 11:00:00) and 5/1/2013 -> open (which should register in the sum as 5/1/2013 to 7/1/2013 11:00:00)
The columns are not indexed.
The TSQL which I have come up with looks like this
SELECT
SUM (
CASE
WHEN NULLIF(PauseEnd,'20991231') IS NULL THEN
DATEDIFF(mi, PauseStart, ISNULL(NULLIF(PauseEnd,'20991231'), GetDate()))
WHEN PauseEnd > GetDate() THEN
DATEDIFF(mi, PauseStart, GetDate())
ELSE
DATEDIFF(mi, PauseStart, ISNULL(NULLIF(PauseEnd,'20991231'), GetDate()))
END
) AS Datedifference
FROM Pauses
WHERE Pauses.RecID = 10
AND PauseStart < GetDate()
This gives me the results
4021
1440
3533
which seem correct, however my question remains,
Is this the most efficient way of achieving this result?
addendum, this table could start holding millions of records, so I'd like to make the tsql that calculates the sum efficient in the first instance.
I would do it like this:
SELECT
PauseStart,
DATEDIFF(mi, PauseStart, CASE WHEN PauseEnd > GetDate() THEN GetDate() ELSE PauseEnd END) as Datedifference
FROM Pauses
WHERE Pauses.RecID = 10 AND PauseStart < GetDate()

Resources