Time difference between two times in Hours in SQL Server - sql

I need the time difference between two times in Hours. I have the start time and end time as shown below:
Start time | End Time
-----------+----------
23:00:00 | 19:00:00
23:00:00 | 07:00:00
I need the output for first row as 20, for second row 8.

Try this:
Schema:
create table a(Starttime time,Endtime time)
INSERT INTO a VALUES ('23:00:00','19:00:00')
INSERT INTO a VALUES ('09:00:00','19:00:00')
INSERT INTO a VALUES ('23:00:00','07:00:00')
Query:
select Starttime,Endtime,
CASE WHEN datediff(HOUR,Starttime,Endtime)<0 THEN 24+datediff(HOUR,Starttime,Endtime)
ELSE datediff(HOUR,Starttime,Endtime) END Diff
FROM A
Output:
| Starttime | Endtime | Diff |
|------------------|------------------|------|
| 23:00:00.0000000 | 19:00:00.0000000 | 20 |
| 09:00:00.0000000 | 19:00:00.0000000 | 10 |
| 23:00:00.0000000 | 07:00:00.0000000 | 8 |

Use DATEDIFF:
SELECT
start_time,
end_time,
24 + DATEDIFF(HOUR, start_time, end_time) AS diff_in_hours
FROM yourTable;
Demo

Query as per your requirement, just put your table name at the place of "YourTable"
SELECT Starttime
,Endtime
,CASE
WHEN DATEDIFF(HOUR, Starttime, Endtime) < 0
THEN 24 + DATEDIFF(HOUR, Starttime, Endtime)
ELSE DATEDIFF(HOUR, Starttime, Endtime)
END Time_Difference
FROM YourTable

Use select case
select case when start_time > end_time
then datediff(hour, start_time , dateadd(hh, 24, end_Time))
else datediff(hh, start_time , end_Time) end

Related

Splitting up events that occur over the day boundary

I have a table of events with a start time and an end time, with some events that have a start time before midnight and an end time after midnight. I'd like to produce output that splits up these events at the midnight barrier so they can be counted toward their respective date.
| EVENT_ID | START_TIME | END_TIME |
|----------|-------------------------|-------------------------|
| 1001 | 2021-02-21 14:00:00.000 | 2021-02-21 18:00:00.000 |
| 1002 | 2021-02-21 17:00:00.000 | 2021-02-22 03:00:00.000 |
| 1003 | 2021-02-21 18:00:00.000 | 2021-02-21 22:00:00.000 |
| 1004 | 2021-02-21 22:00:00.000 | 2021-02-22 07:00:00.000 |
The above table could be produced by the query:
SELECT EVENT_ID,
START_TIME,
END_TIME
FROM EVENTS
WHERE START_TIME BETWEEN '2021-02-21 00:00:00.000' AND '2021-02-21 23:59:59.999'
;
My desired output will split up the events that span multiple days at midnight:
| EVENT_ID | START_TIME | END_TIME |
|----------|-------------------------|-------------------------|
| 1001 | 2021-02-21 14:00:00.000 | 2021-02-21 18:00:00.000 |
| 1002 | 2021-02-21 17:00:00.000 | 2021-02-21 23:59:59.999 |
| 1002 | 2021-02-22 00:00:00.000 | 2021-02-22 03:00:00.000 |
| 1003 | 2021-02-21 18:00:00.000 | 2021-02-21 22:00:00.000 |
| 1004 | 2021-02-21 22:00:00.000 | 2021-02-21 23:59:59.999 |
| 1004 | 2021-02-22 00:00:00.000 | 2021-02-22 07:00:00.000 |
Any help would be greatly appreciated. Ideally I'd like to produce this without functions or the creation of new tables.
Note that I'm using SQL Server 2016
Using table of numbers
with t0(n) as (
select n
from (
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
) t(n)
),nmbs as(
select row_number() over(order by t1.n) - 1 n
from t0 t1 cross join t0 t2 cross join t0 t3
)
select event_id,
case when n = 0
then start_time
else dateadd(day, n, convert(date, start_time))
end start_time,
case when datediff(day, start_time, end_time) = n
then end_time
else dateadd(second, -1, dateadd(day, n + 1, convert(datetime, convert(date, start_time))))
end as end_time
from Events
cross apply (
select top (datediff(day, start_time, end_time) + 1) n
from nmbs) ns
You can use a recursive CTE for this:
with cte as (
select event_id, start_time,
(case when datediff(day, start_time, end_time) = 0 then end_time
else dateadd(day, 1, convert(date, start_time))
end) as end_time,
end_time as real_end_time
from t
union all
select event_id, end_time,
(case when dateadd(day, 1, convert(date, end_time)) > real_end_time
then real_end_time
else dateadd(day, 1, convert(date, end_time))
end),
real_end_time
from cte
where end_time < real_end_time
)
select *
from cte;
Here is a db<>fiddle.
The following method solves for the case of midnight between START_TIME and END_TIME. The "desired output" above indicates only a single midnight occurs between START_TIME and END_TIME.
IF OBJECT_ID('tempdb..#t') IS NOT NULL DROP TABLE #t
CREATE TABLE #t ( Event_ID INT, START_TIME DATETIME2, END_TIME DATETIME2)
INSERT INTO #t (Event_ID, START_TIME, END_TIME)
VALUES
( 1001, '2021-02-21 14:00:00.000', '2021-02-21 18:00:00.000' )
, ( 1002, '2021-02-21 17:00:00.000', '2021-02-22 03:00:00.000' )
, ( 1003, '2021-02-21 18:00:00.000', '2021-02-21 22:00:00.000' )
, ( 1004, '2021-02-21 22:00:00.000', '2021-02-22 07:00:00.000' )
-- get original data plus midnight after START_TIME
IF OBJECT_ID('tempdb..#stage') IS NOT NULL DROP TABLE #stage
SELECT *
, CONVERT(DATETIME2, CONVERT(DATE, DATEADD(DAY, 1, t.START_TIME))) d
INTO #stage
FROM #t t
-- get all rows
SELECT Event_ID, START_TIME
, CASE WHEN d > END_TIME THEN END_TIME ELSE d END END_TIME
FROM #stage
UNION ALL
-- get rows where midnight occurs between START_TIME and END_TIME
SELECT Event_ID
, CASE WHEN d > END_TIME THEN START_TIME ELSE d END START_TIME
, END_TIME
FROM #stage
WHERE d < END_TIME
ORDER BY Event_ID

Fetch empty/open time ranges in postgresql

I'm trying to find open shifts where:
First shift starts at 6 AM
Last Shift ends at 12 AM
ie:
Given the following data/day:
start_time | end_time
-----------|---------
9 AM | 3 PM
5 PM | 10 PM
Expected results:
start_time | end_time
-----------|---------
6 AM | 9 AM
3 PM | 5 PM
10 PM | 12 AM
Here's what I tried but it's not working (Ik it's mostly way far from the correct answer)
SELECT *
FROM WORKERS_SCHEDULE
WHERE START_TIME not BETWEEN
ANY (SELECT START_TIME FROM WORKERS_SCHEDULE)
AND (SELECT START_TIME FROM WORKERS_SCHEDULE)
start_time and end_time are of datatype TIME.
Here is one way to do it with union all and window functions:
select *
from (
select '06:00:00'::time start_time, min(start_time) end_time from mytable
union all
select end_time, lead(start_time) over(order by start_time) from mytable
union all
select max(end_time), '23:59:59'::time from mytable
) t
where start_time <> end_time
It is bit complicated to thouroughly explain how it works but: the first unioned query computes the interval between 6 AM and the start of the first shift, the second subquery processes declared shift, and the last one handles the interval between the last shift and midnight. Then, the outer query filters on records that have gaps. To understand how it works, you can run the subquery independently, and see how the starts and ends ajust.
Demo on DB Fiddle:
start_time | end_time
:--------- | :-------
06:00:00 | 09:00:00
15:00:00 | 17:00:00
22:00:00 | 23:59:59
Try this if it works for you
SELECT
Case when
START_TIME=(SELECT
MIN(start_time) FROM
TABLE) AND START_TIME >'6:00
AM'
THEN
'6:00 AM -' ||MIN(START_TIME)
ELSE
SELECT min(END_TIME) FROM
TABLE
WHERE ENDTIME<S.START_time
||StartTime
End
From table S
Union
(select max(endtime) ||
' 12:00 AM' from table)
This is another way:
WITH RECURSIVE open_shifts AS (
SELECT time '6:00' AS START_TIME, MIN(START_TIME) AS END_TIME
FROM WORKERS_SCHEDULE
WHERE START_TIME BETWEEN time '6:00' AND time '23:59'
UNION
SELECT start_gap.START_TIME AS START_TIME, end_gap.END_TIME AS END_TIME FROM WORKERS_SCHEDULE end_gap,
(SELECT ws.END_TIME AS START_TIME
FROM WORKERS_SCHEDULE ws, open_shifts prev_gap
WHERE ws.START_TIME = prev_gap.END_TIME) start_gap
WHERE end_gap.END_TIME > start_gap.START_TIME
AND END_TIME BETWEEN time '6:00' AND time '23:59'
)
SELECT * FROM open_shifts
UNION
SELECT MAX(END_TIME) AS START_TIME, time '23:59' AS END_TIME FROM WORKERS_SCHEDULE
WHERE END_TIME BETWEEN time '6:00' AND time '23:59';
Used a recursive CTE to find the gaps between each shift's end time and the next closest shift's start time. This probably won't work with overlapping shifts though.

SQL Query to convert number value into date

In my transaction table has id Number(11), name Varchar2(25) , transactiondate number(22).
Need to write SQL query to fetch the transaction details. transactiondate should be return as date & time format instead of number.
transaction table
ID Name transactiondate
1 AAA 2458010
2 BBB 2458351
3 CCC 2458712
I got the below result when i execute the below query
Select * from transaction where transactiondate <= TOCHAR(todate('2019/09/17 00:00:00', 'YYYY/MM/DD hh24:mi:ss') , 'J');
ID Name transactiondate
1 AAA 2458010
2 BBB 2458351
I got the query syntax error when i tried execute the below query
Select name, convert(datetime, convert(varchar(10), transactiondate)) as txndateformat
from transaction;
Expecting query that has to be return name and transactiondate as date format instead of number.
I got below result when i execute the below query
Desc transaction;
Name Null? Type
Id Not Null Number(19)
Name Not Null VarChar2(100)
transactiondate Not Null Number(22)
It all depends on when you are measuring time zero from and what your units are.
Here are some typical solutions:
Oracle Setup:
CREATE TABLE transaction ( ID, Name, transactiondate ) AS
SELECT 1, 'AAA', 2456702 FROM DUAL UNION ALL
SELECT 2, 'BBB', 2456703 FROM DUAL
Query:
SELECT name,
TO_DATE( transactiondate, 'J' )
AS julian_date,
DATE '1970-01-01' + NUMTODSINTERVAL( transactiondate / 1000, 'SECOND' )
AS unix_timestamp,
DATE '1970-01-01' + NUMTODSINTERVAL( transactiondate, 'SECOND' )
AS seconds_since_1970,
DATE '1970-01-01' + NUMTODSINTERVAL( transactiondate, 'MINUTE' )
AS minutes_since_1970,
DATE '1970-01-01' + NUMTODSINTERVAL( transactiondate, 'HOUR' )
AS hours_since_1970,
DATE '1900-01-01' + NUMTODSINTERVAL( transactiondate, 'HOUR' )
AS hours_since_1900,
DATE '1899-12-30' + transactiondate
AS excel_date
FROM transaction
Output:
NAME | JULIAN_DATE | UNIX_TIMESTAMP | SECONDS_SINCE_1970 | MINUTES_SINCE_1970 | HOURS_SINCE_1970 | HOURS_SINCE_1900 | EXCEL_DATE
:--- | :------------------ | :------------------ | :------------------ | :------------------ | :------------------ | :------------------ | :------------------
AAA | 2014-02-13 00:00:00 | 1970-01-01 00:40:56 | 1970-01-29 10:25:02 | 1974-09-03 01:02:00 | 2250-04-05 14:00:00 | 2180-04-04 14:00:00 | 8626-03-21 00:00:00
BBB | 2014-02-14 00:00:00 | 1970-01-01 00:40:56 | 1970-01-29 10:25:03 | 1974-09-03 01:03:00 | 2250-04-05 15:00:00 | 2180-04-04 15:00:00 | 8626-03-22 00:00:00
db<>fiddle here
(Note: Excel dates are slightly more complicated if you want to support values before 1900-03-01 but most people do not need this so there is only the simplified version included above.)
I assume that numbers are epoch numbers.
For SQL Server:
SELECT DATEADD(ss, 2456702, '19700101') --ss means interval = seconds
For Oracle:
select to_date('19700101', 'YYYYMMDD') + ( 1 / 24 / 60 / 60) * 2456702
from dual;

Group by data to get count between the datetime range

Let's say I've a table like below
start_time end_time user_name
2019-01-01 00:00:05 2019-01-01 00:05:05 user1
2019-01-01 00:01:35 2019-01-01 00:06:05 user2
2019-01-01 00:02:05 2019-01-01 00:07:05 user3
2019-01-01 00:03:05 2019-01-01 00:08:05 user1
2019-01-01 00:04:05 2019-01-01 00:09:05 user2
My objective is find out how many users were logged in for a MINUTE. Say like below
time active no of users
2019-01-01 00:00:00 1
2019-01-01 00:01:00 2
2019-01-01 00:02:00 3
2019-01-01 00:03:00 3
2019-01-01 00:04:00 3
Now I first tried to round of time for a new column dateadd(mi, datediff(mi, 0, dateadd(s, 30, start_time)), 0). So, I will receive like above table time column
Next I tried to find the count for rounded datetime like below
SELECT
dateadd(mi, datediff(mi, 0, dateadd(s, 30, start_time)), 0) as RoundedDateTime,
(
SELECT count(distinct(user_name))
FROM entrytable sh
WHERE (sh.end_time > dateadd(mi, datediff(mi, 0, dateadd(s, 30, t.start_time)), 0)
and sh.start_time <= dateadd(mi, datediff(mi, 0, dateadd(s, 30, t.start_time)), 0))
) as usercounter
FROM entrytable t
But, above SQL query is running for longer time and goes to not responding mode.
I could not fix the issue. Can someone help?
Thanks in advance!
The most trivial solution is this:
DECLARE #t TABLE (start_time datetime, end_time datetime, user_name varchar(10));
INSERT INTO #t VALUES
('2019-01-01 00:00:05', '2019-01-01 00:05:05', 'user1'),
('2019-01-01 00:01:35', '2019-01-01 00:06:05', 'user2'),
('2019-01-01 00:02:05', '2019-01-01 00:07:05', 'user3'),
('2019-01-01 00:03:05', '2019-01-01 00:08:05', 'user1'),
('2019-01-01 00:04:05', '2019-01-01 00:09:05', 'user2');
SELECT dt AS date_time, SUM(SUM(val)) OVER (ORDER BY dt) AS active_count
FROM (
SELECT start_time, +1 FROM #t UNION ALL
SELECT end_time, -1 FROM #t
) cte1(dt, val)
GROUP BY dt
This will give you the number of active users whenever there was a change (someone logged in or logged out). Result:
| date_time | active_count |
|-------------------------|--------------|
| 2019-01-01 00:00:05.000 | 1 |
| 2019-01-01 00:01:35.000 | 2 |
| 2019-01-01 00:02:05.000 | 3 |
| 2019-01-01 00:03:05.000 | 4 |
| 2019-01-01 00:04:05.000 | 5 |
| 2019-01-01 00:05:05.000 | 4 |
| 2019-01-01 00:06:05.000 | 3 |
| 2019-01-01 00:07:05.000 | 2 |
| 2019-01-01 00:08:05.000 | 1 |
| 2019-01-01 00:09:05.000 | 0 |
Be advised that the result does not contain the "in-between" dates.
This question was originally tagged for SQL Server 2012, so this answer is for SQL Server.
One method is to generate a list of minutes and then:
with minutes as (
select cast('2019-01-01 00:00:00' as datetime) as mm
union all
select dateadd(minute, 1, minute)
from cte
where mm < '2019-01-01 00:00:05'
)
select m.*,
(select count(*)
from entrytable et
where et.start_time <= m.mm and
et.end_time > m.mm
) as num_actives
from minutes m;

SQL Sum duration by hour and day

I am trying to write a query that will convert a duration field into seconds and then sum the durations based on what time and day it is.
The duration is the amount of time that the "event" was running during the hour of the end date.
I have this:
Description | Start Date | End Date | Duration
---------------------------------------------------------------------------
ABC | 2015-08-17 10:30:30.000 | 2015-08-17 11:59:59.000 | 0 00:59:59.0
ABC | 2015-08-18 11:00:00.000 | 2015-08-18 11:30:00.000 | 0 00:30:00.0
DEF | 2015-08-17 08:25:00.000 | 2015-08-17 10:30:00.000 | 0 00:30::00.0
ABC | 2015-08-18 11:30:00.000 | 2015-08-18 11:59:59.000 | 0 00:29:59.0
And I'm trying to get something like this:
Description | Date | Hour | Duration
-------------------------------------------
ABC | 2015-08-17 | 11 | 3575
ABC | 2015-08-18 | 11 | 3575
DEF | 2015-08-17 | 10 | 1800
This is the query I have wrote:
SELECT Description,
DATEPART(HOUR, EndDT), SUM(DATEPART(SECOND, CONVERT(TIME, RIGHT(Duration, LEN(Duration) -2))) +
60 * DATEPART(MINUTE, CONVERT(TIME, RIGHT(Duration, LEN(Duration) - 2))) +
3600 * DATEPART(HOUR, CONVERT(TIME, RIGHT(Duration, LEN(Duration) - 2))))
FROM table
GROUP BY Description,
DATEPART(HOUR, EndDT),
DATEADD(d, 0, DATEDIFF(d, 0, EndDT));
This query doesn't seem to be taking days into consideration like I thought it would and I have no idea how fix it.
I am getting something like this:
Description | Hour | Duration
------------------------------
ABC | 11 | 7150
DEF | 10 | 1800
I also realise that I haven't got the date in the select statement at the moment, but that can be added later.
If you GROUP your data by EndDT date part only, you get what you need:
WITH T AS (
SELECT *
FROM (VALUES('ABC', CAST('2015-08-17 10:30:30.000' AS DATETIME), CAST('2015-08-17 11:59:59.000' AS DATETIME), '0 00:59:59.0'),
('ABC', CAST('2015-08-18 11:00:00.000' AS DATETIME), CAST('2015-08-18 11:30:00.000' AS DATETIME), '0 00:30:00.0'),
('DEF', CAST('2015-08-17 08:25:00.000' AS DATETIME), CAST('2015-08-17 10:30:00.000' AS DATETIME), '0 00:30:00.0'),
('ABC', CAST('2015-08-18 11:30:00.000' AS DATETIME), CAST('2015-08-18 11:59:59.000' AS DATETIME), '0 00:29:59.0'))
AS V(Description, StartDT, EndDT, Duration)
)
SELECT Description ,
CAST(EndDT AS DATE),
DATEPART(HOUR, EndDT) ,
SUM(DATEPART(SECOND, CONVERT(TIME, RIGHT(Duration, LEN(Duration) - 2)))
+ 60 * DATEPART(MINUTE,CONVERT(TIME, RIGHT(Duration, LEN(Duration) - 2)))
+ 3600 * DATEPART(HOUR, CONVERT(TIME, RIGHT(Duration, LEN(Duration) - 2))))
FROM T
GROUP BY
Description ,
CAST(EndDT AS DATE),
DATEPART(HOUR, EndDT)
For this i am getting:
ABC 2015-08-17 11 3599
ABC 2015-08-18 11 3599
DEF 2015-08-17 10 1800
You can ignore the CTE that holds data from the example above, just added it to provide a working example.
This gives you how many hours and seconds each event was running for each day.
SELECT Description,
DATEDIFF(s,MIN([Start Date]), MAX([End Date]))/60/60 AS Hour,
DATEDIFF(s,MIN([Start Date]), MAX([End Date])) AS Duration
FROM yourtable
GROUP BY Description, DAY([Start Date])
OUTPUT
Description Hour Duration
ABC 1 5369
DEF 2 7500
ABC 0 3599
SQL Fiddle: http://sqlfiddle.com/#!3/935c7/15/0