SQL DateDiff in Minutes Spanning Over Night - sql

Trying to get the difference in minutes between a datetime field and a time field. I'm using the datediff function.
When the start time starts on one date like '2018-01-08 22:35:55.043' and the end time is the following day like '00:35:56.2136644', the result is counting from the end time to the start time.
Examples:
select DATEDIFF(MINUTE, CAST('2018-01-08 22:35:55.043' AS TIME), '00:35:56.2136644') AS minDiff1
select DATEDIFF(MINUTE, '00:35:56.2136644', CAST('2018-01-08 22:35:55.043' AS TIME)) AS minDiff2
select DATEDIFF(MINUTE, CONVERT(TIME, '2018-01-08 22:35:55.043'), '00:35:56.2136644') AS minDiff3
select DATEDIFF(MINUTE, '00:35:56.2136644', CONVERT(TIME, '2018-01-08 22:35:55.043')) AS minDiff4
The results were different from what I was expecting. The desired result would be 120 minutes.
minDiff1 = -1320
minDiff2 = 1320
minDiff3 = -1320
minDiff4 = 1320
Original Query
select DATEDIFF(MINUTE, CAST(test_start_datetime as TIME), test_end_time) AS minDiff
from user_exam

The following assumes that the time belongs to the same day or the very next day:
SELECT *, CASE
-- same day -- start time is less than end time
WHEN CAST(datetimecol AS time) <= timecol THEN DATEDIFF(MINUTE, CAST(datetimecol AS time), timecol)
-- next day -- start time is more than end time (it rolled over into next day)
ELSE 1440 - DATEDIFF(MINUTE, timecol, CAST(datetimecol AS time))
END
FROM (VALUES
(CAST('2018-01-08 22:35:55.043' AS DATETIME), CAST('22:35:55.0433333' AS TIME)),
(CAST('2018-01-08 22:35:55.043' AS DATETIME), CAST('23:35:56.2136644' AS TIME)),
(CAST('2018-01-08 22:35:55.043' AS DATETIME), CAST('00:35:56.2136644' AS TIME))
) AS tests(datetimecol, timecol)
In the above example 1440 is the number of minutes in 24 hours.
Demo on DB Fiddle

Let us first reconsider your examples:
select DATEDIFF(MINUTE, CAST('2018-01-08 22:35:55.043' AS TIME), '00:35:56.2136644') AS minDiff1
select DATEDIFF(MINUTE, '00:35:56.2136644', CAST('2018-01-08 22:35:55.043' AS TIME)) AS minDiff2
select DATEDIFF(MINUTE, CONVERT(TIME, '2018-01-08 22:35:55.043'), '00:35:56.2136644') AS minDiff3
select DATEDIFF(MINUTE, '00:35:56.2136644', CONVERT(TIME, '2018-01-08 22:35:55.043')) AS minDiff4
Converting/casting from datetime to time throws the date part away. Thus, you are actually running:
select DATEDIFF(MINUTE, '22:35:55.043'', '00:35:56.2136644') AS minDiff1
select DATEDIFF(MINUTE, '00:35:56.2136644', '22:35:55.043') AS minDiff2
select DATEDIFF(MINUTE, '22:35:55.043', '00:35:56.2136644') AS minDiff3
select DATEDIFF(MINUTE, '00:35:56.2136644'22:35:55.043') AS minDiff4
At this point, you seem to not be considering the fact that DATEDIFF is directed, meaning it counts (first argument units passed) FROM second argument TO third argument. Thus, since 00:35 is 1320 minutes earlier than 22:35 (of the same day), 00:35 -> 22:35 returns 1320, whereas 22:35 -> 00:35 returns -1320.
To be completely precise, since DATEDIFF uses datetimes, your times/time-representing strings are implicitly converted to dates. Since a date is not provided by you, the date used is the one with value 0: 1st January 1900. That's the common day the function acts upon.

If you want to see 120 that's mean time difference is not enough for your purpose. With your question you want to find difference between 22:35 and following day 00:35
You need to find DATETIME difference something like that:
SELECT DATEDIFF(MINUTE, CAST(GETDATE() AS DATETIME)
+CAST(CAST('22:35:55.2136644' AS TIME) AS DATETIME)
,
CAST(GETDATE() AS DATETIME)+1
+CAST(CAST('00:35:56.2136644' AS TIME)AS DATETIME)
) AS minDiff1

Related

Time Difference between Two rows excluding weekend and Time stamp

Date
ID
Value
2022-10-07 17:30:00.000
1
1
2022-10-10 10:00:00.000
2
2
2022-10-12 08:31:42.000
3
1
I want to find date difference from two rows with few conditions in MS SQL
Difference should be from 9am - 6pm (exclude rest of the time)
Also Exclude weekends.
For example, Date Diff from first row will be 1 hr 30 mins.
I am using this query:
DATEDIFF(MINUTE, LAG(date) OVER (ORDER BY date), date)
How can I add more conditions to this?
First things first, let's compute the values we need to handle, namely the current date (that we already have as a field) and the previous date (using the LAG window function, as you did in your attempt).
WITH cte AS (
SELECT [date] AS curDate,
LAG([date]) OVER(ORDER BY date) AS prevDate
FROM tab
)
CASE 1: prevDate's day is equal to curDate's day
In this case we just need to apply the difference in minutes between the two dates as follows:
CASE WHEN CONVERT(DATE, curDate) = CONVERT(DATE, prevDate)
THEN DATEDIFF(MINUTE, prevDate, curDate)
CASE 2: prevDate's day is not equal to curDate's day
Here we need to compute three things:
the difference in time between endtime (18:00:00) and prevDate's time
DATEDIFF(MINUTE, CAST(prevDate AS TIME), '18:00:00')
the difference in time between starttime (9:00:00) and curDate's time
DATEDIFF(MINUTE, '9:00:00', CAST(curDate AS TIME))
the difference in days between curDate and prevDate. For this last one there's the caveat regarding the weekend days though. We can solve this problem by checking if the day of week of curDate is smaller than the day of week of prevDate (if prevDate's working date is Friday and curDate's working date is Monday, we will have dayofweek = 2 for Monday and dayofweek = 6 for Friday, whereas 2-6<0). If the result is smaller than 0, then we need to subtract 3 days of work (multiplied by 9 days of working activity for each day - 18:00:00-9:00:00).
DATEDIFF(MINUTE, '9:00:00', CAST(curDate AS TIME)) +
DATEDIFF(MINUTE, CAST(prevDate AS TIME), '18:00:00') +
9*(DATEDIFF(DAY, prevDate, curDate) -
CASE WHEN DATEPART(WEEKDAY, curDate) - DATEPART(WEEKDAY, prevDate) > 1
THEN 0
ELSE 3 END
)
Hence the Final Query would be the following one:
WITH cte AS (
SELECT [date] AS curDate,
LAG([date]) OVER(ORDER BY date) AS prevDate
FROM tab
)
SELECT curDate,
CASE WHEN CONVERT(DATE, curDate) = CONVERT(DATE, prevDate)
THEN DATEDIFF(MINUTE, prevDate, curDate)
ELSE DATEDIFF(MINUTE, '9:00:00', CAST(curDate AS TIME)) +
DATEDIFF(MINUTE, CAST(prevDate AS TIME), '18:00:00') +
9*(DATEDIFF(DAY, prevDate, curDate) -
CASE WHEN DATEPART(WEEKDAY, curDate) - DATEPART(WEEKDAY, prevDate) > 1
THEN 0
ELSE 3 END)
END
FROM cte
Check the demo here.

How can I return an event duration divided over multiple days in SQL?

I have a database that contains Event data. These Events represent a warning state in a system. They have a StartTime, EndTime, Duration (in seconds) and VarName (and some other attributes that are less relevant for the question).
I am trying to write a query that will allow me to represent the amount of time that a certain warning was active per day. This way, service engineers can easily see if certain changes/fixes caused a warning to decrease or disappear over time.
A quick-and-dirty first attempt is shown below.
SELECT
[VarName] AS metric,
SUM([Duration]) AS value,
Convert(date, [StartTime]) AS date
FROM [dbo].[Events]
WHERE VarName LIKE 'WARN%'
GROUP BY Convert(date, [StartTime]), VarName
ORDER BY date
This works well enough when Events last only a short time and handles multiple Events of the same type in a day. But it breaks down when Events span multiple days (or even weeks).
Example:
VarName
StartTime
EndTime
Duration
WARN_1
2021-06-28 23:00:00.000
2021-06-29 02:00:00.00
10800
What I get:
metric
date
value
WARN_1
2021-06-28
10800
What I want:
metric
date
value
WARN_1
2021-06-28
3600
WARN_1
2021-06-29
7200
Taking into account that:
An event can occur multiple times in the same day
An event can span multiple days
I'll be fiddling with this today and if I come up with a working solution I'll append it to this post. But I don't work with SQL all that often, and it feels like this may require some more advanced trickery. Any help is appreciated!
You can use a recursive CTE to break the periods into days:
with cte as (
select varname, starttime,
(case when datediff(day, starttime, endtime) = 0
then endtime
else dateadd(day, 1, convert(date, starttime))
end) as day_endtime,
endtime
from t
union all
select varname, day_endtime,
(case when datediff(day, starttime, endtime) = 1
then endtime
else dateadd(day, 1, convert(date, day_endtime))
end) as day_endtime,
endtime
from cte
where datediff(day, starttime, endtime) > 0
)
select *
from cte;
To aggregate, change the last part to:
select varname, convert(date, starttime),
sum(datediff(second, starttime, day_endtime))
from cte
group by varname, convert(date, starttime);
Here is a db<>fiddle.
If you need timespans longer than 100 days, add option (maxrecursion 0) or any number larger than about 732.
If you have a numbers table, you can join to that based on the difference in days which will give you n number of rows for each event based on the days. You then just need to calculate how much of each event falls in that day, which I've done with a case expression:
SELECT t.VarName,
StartDate = CONVERT(DATE, DATEADD(DAY, n.Number, t.StartTime)),
Duration = CASE -- starts and ends on same date
WHEN CONVERT(DATE, t.StartTime) = CONVERT(DATE, t.EndTime) THEN t.Duration
-- First Day
WHEN n.Number = 0 THEN DATEDIFF(SECOND, t.StartTime, CONVERT(DATE, DATEADD(DAY, 1, t.StartTime)))
--Last Day
WHEN CONVERT(DATE, DATEADD(DAY, n.Number, t.StartTime)) = CONVERT(DATE, t.EndTime)
THEN DATEDIFF(SECOND, CONVERT(DATE, DATEADD(DAY, n.Number, t.StartTime)), t.EndTime)
-- Middle Day
ELSE 86400
END
FROM #t AS t
INNER JOIN Numbers AS n
ON n.Number <= DATEDIFF(DAY, t.StartTime, t.EndTime);
If you don't have a numbers table, you can very easily create this on the fly:
DECLARE #T TABLE (VarName VARCHAR(10), StartTime DATETIME, EndTime DATETIME, Duration AS DATEDIFF(SECOND, StartTime, EndTime));
INSERT #T (VarName, StartTime, EndTime)
VALUES
('WARN_1', '20210628 23:00:00.000', '20210629 02:00:00.00'),
('WARN_2', '20210629 11:00:00.000', '20210629 14:00:00.00'),
('WARN_3', '20210630 23:00:00.000', '20210704 02:00:00.00');
-- This will do numbers 0-99, add more cross joins if necessary
WITH Numbers (Number) AS
( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) - 1
FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n1 (N)
CROSS JOIN (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n2 (N)
)
SELECT t.VarName,
StartDate = CONVERT(DATE, DATEADD(DAY, n.Number, t.StartTime)),
Duration = CASE -- starts and ends on same date
WHEN CONVERT(DATE, t.StartTime) = CONVERT(DATE, t.EndTime) THEN t.Duration
-- First Day
WHEN n.Number = 0 THEN DATEDIFF(SECOND, t.StartTime, CONVERT(DATE, DATEADD(DAY, 1, t.StartTime)))
--Last Day
WHEN CONVERT(DATE, DATEADD(DAY, n.Number, t.StartTime)) = CONVERT(DATE, t.EndTime)
THEN DATEDIFF(SECOND, CONVERT(DATE, DATEADD(DAY, n.Number, t.StartTime)), t.EndTime)
-- Middle Day
ELSE 86400
END
FROM #t AS t
INNER JOIN Numbers AS n
ON n.Number <= DATEDIFF(DAY, t.StartTime, t.EndTime);

Convert timestamp to under certain condition in sql

I have timestamp column and for the start time if my time is greater than 5:30Pm I would like to consider the time as 5:00PM and I need to subtract the converted minutes from start date and enddate.
SELECT [starttime], [endtime],
case when FORMAT([starttime],'HH:mm') >'17:30' then 17.00
end as newstarttime,
FORMAT([endtime],'HH:mm') as newendtime
FROM Table1
I tried to convert the timestamp into 24hour format but not sure about the next steps.
Starttime Endtime
2019-08-13 17:40:33:000 2019-08-13 19:00:00:000
I am assuming that you are using SQL Server, and I think you want:
select t1.*,
(case when convert(time, starttime) >= '17:30:00'
then dateadd(hour, 17, convert(datetime, convert(date, starttime)))
else starttime
end) as new_starttime
from table1 t1;
You can also express the date add logic as:
convert(datetime, convert(date, starttime)) + '17:00:00'
I have timestamp field and for the start time if my time is greater than 5:30Pm I would like to consider the time as 5:00PM
To do this, its easier to work with dates than doing character conversion. See the below query:
SELECT [starttime], [endtime]
, case
when FORMAT([starttime],'2020-01-01 HH:mm:00') > '2020-01-01 17:30:00' then '17:00'
else FORMAT([starttime],'HH:mm')
end as newsttime
, FORMAT([endtime],'HH:mm') as newendtime
FROM Table1;
What do you mean by:
I need to subtract the converted minutes from start date and enddate.

Count values in time ranges (SQL)

I am trying to make a sql query in which I want to count the people comprised in three ranges:
Tomorrow (from 06:00 a.m. to 12:00 p.m.)
Afternoon (from 12:00 p.m. to 9:00 p.m.)
Night (from 9:00 p.m. to 6:00 a.m.)
I have two attributes, check in time and check out time which have the following format:
2020-05-20 12:10:29.000
I am doing it in the following way but it does not work, for example, in the night:
select (case when datepart(hour, dateIn) > datepart(hour, '21')) and
datepart(minute, dateIn) > datepart(minute, '00'))
If I understand correctly, you can subtract 6 hours to get the date and then use time comparisons for the shift. So, I think you want:
select convert(date, dateadd(hour, -6, t.datein)), v.shift, count(*)
from t cross apply
(values (case when convert(time, t.datein) < '06:00:00' then 'night'
when convert(time, t.datein) < '12:00:00' then 'morning'
when convert(time, t.datein) < '21:00:00' then 'afternoon'
else 'night'
end)
) v(shift)
group by convert(date, dateadd(hour, -6, datein)), v.shift;

how to select using where clause?

I have two dates, From date and To Date.
Also i have two time fields, From Time and To Time.
The date field in the database is Datetime. I need to select data according to both date and time.
This is my query for selecting data between 13:00 to 15:00, but it is not suitable for 20:00 to 08:00.
where Date>= '2/01/2012' AND Date<'2/28/2013'
AND CAST(Date AS TIME) BETWEEN '20:00' AND '08:00'
Without seeing your specific error/unexpected results, I think the problem is that 20 is greater than 8.
You'll have to use two conditions:
where Date>= '2/01/2012' AND Date<'2/28/2013' AND (CAST(Date AS TIME) > '20:00' OR CAST(Date AS TIME) < '08:00')
EDIT: fixed condition
Is this what you are after?
WHERE Date BETWEEN '2012-01-01 20:00:00.000' AND '2012-12-01 08:00:00.000'
It is a little bit unclear whether you are attempting to generate the WHERE clause variables dynamically?
You need to combine your "date" and "time" parts together.
This code will illustrate how to do this:
SELECT the_date
, the_time
, DateAdd(hh, DatePart(hh, the_time), the_date) As hour_added
, DateAdd(mi, DatePart(mi, the_time), the_date) As minute_added
, DateAdd(mi, DatePart(mi, the_time), DateAdd(hh, DatePart(hh, the_time), the_date)) As both_added
FROM (
SELECT Cast('2013-02-28' As datetime) As the_date
, Cast('08:30' As datetime) As the_time
) As example
You can then use the resultant values in your comparison