Oracle average difference between two date fields - sql

I have a table in database like this:
unit arrival_date departure_date
---- ------------- --------------
1 27/1/2017 08:01:20 a. m. 27/1/2017 08:04:27 a. m.
1 27/1/2017 08:05:35 a. m. 27/1/2017 08:09:28 a. m.
I need to calculate the average time difference between arrival_date and departure_date of users and show the result as hour,minute,second format(HH:MI:SS).
If I made this arrival_date - departure_date, I get the result in days, but I am struggling to get the average in hour, minutes and seconds.
The fields are DATE fields not TIMESTAMP fields.

Here's an example.
When subtracting two date datatype values, the result is number of days. It shows the INTER CTE. When you multiply it by 24 (number of hours in a day), 60 (number of minutes in an hour) and 60 (number of seconds in a minute), the result is number of seconds (DIFF_SECS).
AVERAGES CTE shows how to apply AVG function to previous results; nothing special in that, just pay attention that you have to GROUP it BY the UNIT column.
Finally, apply TO_CHAR formatting to calculation (some TRUNC and MOD calls in order to extract hours, minutes and seconds from the AVG_DIFF_SECS value).
I suggest you run each CTE separately, step by step, to easier follow the execution.
SQL> with test (unit, arr, dep) as
2 (select 1, to_date('27.01.2017 08:01:20', 'dd.mm.yyyy hh24:mi:ss'),
3 to_date('27.01.2017 08:04:27', 'dd.mm.yyyy hh24:Mi:ss')
4 from dual union all
5 select 1, to_date('27.01.2017 08:05:35', 'dd.mm.yyyy hh24:mi:ss'),
6 to_date('27.01.2017 08:09:28', 'dd.mm.yyyy hh24:Mi:ss')
7 from dual
8 ),
9 inter as
10 (select unit, (dep - arr) diff_days,
11 (dep - arr) * 24 * 60 * 60 diff_secs
12 from test
13 ),
14 averages as
15 (select unit,
16 avg(dep - arr) avg_diff_days,
17 avg((dep - arr) * 24 * 60 * 60) avg_diff_secs
18 from test
19 group by unit
20 )
21 select
22 to_char(trunc(avg_diff_secs / 3600), 'fm00') || ':' || -- hours
23 to_char(trunc(mod(avg_diff_secs , 3600) / 60), 'fm00') || ':' || -- minutes
24 to_char(mod(avg_diff_secs, 60), 'fm00') -- seconds
25 avg_diff_formatted
26 from averages;
AVG_DIFF_FORMATTED
--------------------
00:03:30
SQL>

select unit, avg(departure_date - arrival_date) as avg_date
from mytable

Related

cast two separate columns which has hour ( datatype number) and minutes ( datatype number) to time datatype and subtract 90 minutes in oracle

I have two separate columns for hours and minutes in my table and I have a report where i should be subtracting 90 minutes from total time put together or ( 1 hour from hour field) and 30 minutes from minutes field. The output can be in minutes or hours.
I tried "to_char ( hours_column -1,'00' ) || ':' || to_char ( minutes_column -30,'00' ) AS "MAX_TIME" " - this fails when I have time like 9:00 I get 8:-30 as the output when I need to get 7:30.
I came up with some sql code with DATEADD and cast functions which worked but it fails when I implement it in Oracle.
Select Substring(Cast(DATEADD(minute, -90, Cast(hourscolumn + ':' + minutes column as Time)) as varchar(20)),1,5) as max_time
Can someone help me to implement the above code in Oracle? I'm just trying to deduct 90 minutes by putting the hours and minutes columns together.
Something like this?
test CTE represents your data. How come you got that (bad) idea? Who/what prevents you from storing 32 hours and 87 minutes into those columns?
query itself contains
time: the way you create a valid date value. It'll fail if hours and/or minutes are invalid (such as previously mentioned 32:87)
subtracted: subtract 90 minutes from time; (24 * 60) represents 24 hours in a day, 60 minutes in an hour. It'll contain both date and time component
the final result is achieved by applying to_char with appropriate format mask (hh24:mi) to the subtracted value
SQL> alter session set nls_Date_format = 'dd.mm.yyyy hh24:mi';
Session altered.
SQL> with test (hours, minutes) as
2 (select '09', '00' from dual union all
3 select '23', '30' from dual union all
4 select '00', '20' from dual
5 )
6 select hours,
7 minutes,
8 to_date(hours||minutes, 'hh24mi') time,
9 --
10 to_date(hours||minutes, 'hh24mi') - 90 / (24 * 60) subtracted,
11 --
12 to_char(to_date(hours||minutes, 'hh24mi') - 90 / (24 * 60), 'hh24:mi') result
13 from test;
HO MI TIME SUBTRACTED RESUL
-- -- ---------------- ---------------- -----
09 00 01.07.2019 09:00 01.07.2019 07:30 07:30
23 30 01.07.2019 23:30 01.07.2019 22:00 22:00
00 20 01.07.2019 00:20 30.06.2019 22:50 22:50
SQL>
Use NUMTODSINTERVAL to convert the hours and minutes to INTERVAL data types and then you can subtract INTERVAL '90' MINUTE and EXTRACT the resulting hour and minute components.
Oracle Setup:
CREATE TABLE table_name ( hours_column, minutes_column ) AS
SELECT 0, 0 FROM DUAL UNION ALL
SELECT 1, 30 FROM DUAL UNION ALL
SELECT 2, 45 FROM DUAL UNION ALL
SELECT 3, 0 FROM DUAL UNION ALL
SELECT 27, 59 FROM DUAL
Query:
SELECT EXTRACT( HOUR FROM time ) + EXTRACT( DAY FROM time ) * 24 AS hours,
EXTRACT( MINUTE FROM time ) AS minutes,
time,
TO_CHAR( EXTRACT( HOUR FROM time ) + EXTRACT( DAY FROM time ) * 24, '00' )
|| ':' || TO_CHAR( ABS( EXTRACT( MINUTE FROM time ) ), 'FM00' ) AS as_string
FROM (
SELECT NUMTODSINTERVAL( hours_column, 'HOUR' )
+ NUMTODSINTERVAL( minutes_column, 'MINUTE' )
- INTERVAL '90' MINUTE AS time
FROM table_name
)
Output:
HOURS | MINUTES | TIME | AS_STRING
----: | ------: | :---------------------------- | :--------
-1 | -30 | -000000000 01:30:00.000000000 | -01:30
0 | 0 | +000000000 00:00:00.000000000 | 00:00
1 | 15 | +000000000 01:15:00.000000000 | 01:15
1 | 30 | +000000000 01:30:00.000000000 | 01:30
26 | 29 | +000000001 02:29:00.000000000 | 26:29
db<>fiddle here

Extract equivalent HOUR of a Time duration

I have the following date field format: "HH:MM", which represents the duration of an event, and I'd like to extract the equivalent value in HOUR (example: 2h:30min ==> result : 2,5 hour).
Ideally, change your table to store the data as an INTERVAL DAY TO SECOND data type and then you can just store INTERVAL '2:30' HOUR TO MINUTE and use date arithmetic to get your answer.
SELECT ( DATE '1970-01-01' + your_interval_value - DATE '1970-01-01' ) * 24
FROM DUAL;
Since you are storing strings instead of intervals then you can use NUMTODSINTERVAL and string functions to convert the hours and minutes to intervals and then use the same date arithmetic:
db<>fiddle here
Oracle Setup:
CREATE TABLE table_name ( value ) AS
SELECT '2h:30min' FROM DUAL UNION ALL
SELECT '15min' FROM DUAL UNION ALL
SELECT '3h' FROM DUAL UNION ALL
SELECT '26 h : 20 min' FROM DUAL UNION ALL
SELECT '-2h:30min' FROM DUAL UNION ALL
SELECT '-4h' FROM DUAL UNION ALL
SELECT '-45min' FROM DUAL UNION ALL
SELECT '0h:0min' FROM DUAL UNION ALL
SELECT '0h' FROM DUAL UNION ALL
SELECT '-0min' FROM DUAL;
Query:
SELECT value,
( DATE '1970-01-01'
+ NUMTODSINTERVAL(
CASE WHEN INSTR( value, '-' ) > 0 THEN -1 ELSE 1 END
*
TO_NUMBER( COALESCE( REGEXP_SUBSTR( value, '(\d*)\s*h', 1, 1, 'i', 1 , '0' ) ),
'HOUR'
)
+ NUMTODSINTERVAL(
CASE WHEN INSTR( value, '-' ) > 0 THEN -1 ELSE 1 END
*
TO_NUMBER( COALESCE( REGEXP_SUBSTR( value, '(\d*)\s*min', 1, 1, 'i', 1 ), '0' ) ),
'MINUTE'
)
- DATE '1970-01-01'
) * 24 AS hours_difference
FROM table_name;
Output:
VALUE | HOURS_DIFFERENCE
:------------ | ----------------------------------------:
2h:30min | 2.5
15min | .2500000000000000000000000000000000000008
3h | 3
26 h : 20 min | 26.33333333333333333333333333333333333328
-2h:30min | -2.5
-4h | -4
-45min | -.75
0h:0min | 0
0h | 0
-0min | 0
An oracle way:
SELECT (TO_DATE('2h:30min', 'HH24"h":MI"min"') - TO_DATE('0:00', 'HH24:MI')) * 24.0 from dual
How it works:
Parses 2h:30min into a date, and generates a date of 01-JAN-19 (all date parsing without a day, month, year becomes the 1st of the current month/year)
Parses 0:00 to a date -> same day, same month, same year as above, but a time of midnight
Subtracts them, resulting in a floating point number that represents the fractions of a day (a day is 1.0, 12 hours is 0.5 etc) between the two datetimes. This works out in this case as approx 0.1041667
We multiply by 24 to turn our 0.1041667 days into hours -> 2.5
If the value is stored as a string, then you can extract the components and use arithmetic.
One method to extract the components would be to take the first two character and then the 4th and 5th. regexp_substr() provides a bit more flexibility:
select cast(regexp_substr(yyyymm, '[^:]+', 1, 1) as number) + cast(regexp_substr(yyyymm, '[^:]+', 1, 2) as number) / 60
from (select '24:30' as yyyymm from dual) x

Splitting time into hour intervals in oracle (CTE)

So, my aim is to be able to count time spent on certain activities in hour ranges.
My data contains: start of the certain activity and end of that activity,
for example I know that someone had break from '2019-01-09 17:04:34' to '2019-01-09 19:55:03'.
My aim is to calculate that this person spent 55 minutes on break in interval '17-18', 60 minutes on '18-19' and 55 minutes on '19-20'.
My idea was to always split the source so for the row containing start and and of the activity I would receive as many rows as my time range split in the hour ranges (for this sample data I would receive 3: rows with '2019-01-09 17:04:34' to '2019-01-09 17:59:59', '2019-01-09 18:00:00' to '2019-01-09 18:59:59' and '2019-01-09 19:00:00' to '2019-01-09 19:55:03')
If I could obtain something like that I could manage to count all things I need to. I predict that to obtain this result I should use CTE (as we don't know in how many ranges we need to split time interval), but I have no experience in it.
Hopefully I managed to explain my problem clearly. I work on oracle sql developer.
I'd be very grateful for your help on at least some tips.
Since you mentioned recursion, this uses recursive subquery factoring:
-- CTE for sample data
with your_table (id, start_time, end_time) as (
select 1, timestamp '2019-01-09 17:04:34', timestamp '2019-01-09 19:55:03' from dual
union all
select 2, timestamp '2019-01-09 23:47:01', timestamp '2019-01-10 02:05:03' from dual
union all
select 3, timestamp '2019-01-09 18:01:01', timestamp '2019-01-09 18:02:07' from dual
union all
select 4, timestamp '2019-01-09 13:00:00', timestamp '2019-01-09 14:00:01' from dual
),
-- recursive CTE
rcte (id, hour_period, minutes, period_start_time, end_time, hour_num) as (
select id,
-- first period is the original start hour
extract(hour from start_time),
-- minutes in first period, which can end at the end of that hour, or at original
-- end time if earlier
case when extract(minute from end_time) = 0
and end_time >= cast(trunc(start_time, 'HH') as timestamp) + interval '1' hour
then 60
else extract(minute from
least(cast(trunc(start_time, 'HH') as timestamp) + interval '1' hour, end_time)
- start_time
)
end,
-- calculate next period start
cast(trunc(start_time, 'HH') as timestamp) + interval '1' hour,
-- original end time
end_time,
-- first hour period (for later ordering)
1
from your_table
union all
select id,
-- this period's hour value
extract(hour from period_start_time),
-- minutes in this period - either 60 if we haven't reach the end time yet;
-- or if we have then the number of minutes from the end time
case when end_time < period_start_time + interval '1' hour
then extract(minute from end_time)
else 60
end,
-- calculate next period start
period_start_time + interval '1' hour,
-- original end time
end_time,
-- increment hour period (for later ordering)
hour_num + 1
from rcte
where period_start_time < end_time
)
select id, hour_period, minutes
from rcte
order by id, hour_num;
ID HOUR_PERIOD MINUTES
---------- ----------- ----------
1 17 55
1 18 60
1 19 55
2 23 12
2 0 60
2 1 60
2 2 5
3 18 1
4 13 60
4 14 0
It find finds the amount of time spent in the first hour of the period in the anchor member, then recursively looks at subsequent hours until the end time is reached, increasing the passed-on period end time each time; and in the recursive member it checks whether to use a fixed 60 minutes (if it knows the end time hasn't been reached) or use the actual minutes from the end time.
My example periods include ones that span midnight, cover less than an hour, and that start in the first minute of an hour - and which end in the first minute of an hour, which (in my calculation anyway) ends up with a row for that hour anyway and the number of minutes as zero. You can easily filter that out if you don't want to see it.
It is not entirely clear from your post how you want to handle non-zero seconds components (what combination of rounding and/or truncation). In any case, that can be coded easily, once a complete set of non-contradictory rules is agreed upon.
Other than that, your question consists of two parts: identify the proper hours for each id (each activity or event), and the duration of the part of that event during that hour. In the query below, using the CONNECT BY hierarchical technique, I generate the hours and the duration as an interval day to second. As I said, that can be converted to minutes (between 0 and 60) once you clarify the rounding rules.
with
your_table (id, start_time, end_time) as (
select 1, timestamp '2019-01-09 17:04:34', timestamp '2019-01-09 19:55:03'
from dual union all
select 2, timestamp '2019-01-09 23:47:01', timestamp '2019-01-10 02:05:03'
from dual union all
select 3, timestamp '2019-01-09 18:01:01', timestamp '2019-01-09 18:02:07'
from dual union all
select 4, timestamp '2019-01-09 13:00:00', timestamp '2019-01-09 14:00:01'
from dual
)
select id,
trunc(start_time, 'hh') + interval '1' hour * (level - 1) as hr,
case when level = 1 and connect_by_isleaf = 1
then end_time - start_time
when level = 1
then trunc(start_time, 'hh') + interval '1' hour - start_time
when connect_by_isleaf = 1
then end_time - trunc(end_time, 'hh')
else interval '1' hour
end as duration
from your_table
connect by trunc(start_time, 'hh') + interval '1' hour * (level - 1) < end_time
and prior id = id
and prior sys_guid() is not null
;
Output:
ID HR DURATION
---------- ------------------- -------------------
1 2019-01-09 17:00:00 +00 00:55:26.000000
1 2019-01-09 18:00:00 +00 01:00:00.000000
1 2019-01-09 19:00:00 +00 00:55:03.000000
2 2019-01-09 23:00:00 +00 00:12:59.000000
2 2019-01-10 00:00:00 +00 01:00:00.000000
2 2019-01-10 01:00:00 +00 01:00:00.000000
2 2019-01-10 02:00:00 +00 00:05:03.000000
3 2019-01-09 18:00:00 +00 00:01:06.000000
4 2019-01-09 13:00:00 +00 01:00:00.000000
4 2019-01-09 14:00:00 +00 00:00:01.000000

Human readable elapsed time between many days

I work with Oracle SQL and I have a table with 3 columns: Process, Start Date and End Date. I want to calculate the time duration for every process.
I used this query:
select
(enddate.date_value - startdate.date_value) as duration
from dual
and the result is in days.
For example: The start date is 30.3.2016 17:14:53 and the end date is 8.7.2016 14:51:21
When I use the query, the result is 99.90032407407407407407407407407407407407, but I want result like this:
3 months, 7 days, 21 hours, 36 minutes, 28 seconds.
How can I do that?
This complex query (specialy for DAY !!) :
To compute a correct Day I add Month and 12 * Year to the original date.
with dates as(
select
sysdate as d1,
sysdate-99.90032407407407407407407407407407407407-365 as d2
from
dual
),
dates_parts as (
SELECT
d1,
d2,
EXTRACT(YEAR FROM (d1 - d2) YEAR TO MONTH ) as Year,
EXTRACT(MONTH FROM (d1 - d2) YEAR TO MONTH ) as Month,
EXTRACT(DAY FROM (d1 - d2) DAY TO SECOND ) as Day,
EXTRACT(HOUR FROM cast(d1 as timestamp) - cast(d2 as timestamp)) as Hour,
EXTRACT(MINUTE FROM cast(d1 as timestamp) - cast(d2 as timestamp)) as Minute,
EXTRACT(SECOND FROM cast(d1 as timestamp) - cast(d2 as timestamp)) as Second
FROM dates
)
select
dates_parts.Year,
dates_parts.Month,
dates_parts.Day,
dates_parts.Hour,
dates_parts.Minute,
dates_parts.Second,
EXTRACT(DAY FROM (d1 - ADD_MONTHS(d2,Month+Year*12)) DAY TO SECOND ) as Day_Corrected
from
dates_parts
will produce the different date part :
| YEAR | MONTH | DAY | HOUR | MINUTE | SECOND | DAY_CORRECTED |
|------|-------|-----|------|--------|--------|---------------|
| 1 | 3 | 464 | 21 | 36 | 28 | 7 |
The difference between two DATE values is a number representing the number of days. You seem to want an interval, this can be done using TIMESTAMP values.
select cast(enddate as timestamp) - cast(startdate as timestamp)
from the_table
The result of subtracting a timestamp from a timestamp is an interval.
Formatting of an interval value is however quite tricky in Oracle. See e.g. format interval with to_char
Based on my previous answer you can create an Oracle function sinceHumanReadable:
exemple from https://momentjs.com/docs/#/plugins/preciserange/ produce the same result
moment("2014-01-01 12:00:00").preciseDiff("2015-03-04 16:05:06");
// 1 year 2 months 3 days 4 hours 5 minutes 6 seconds
http://sqlfiddle.com/#!4/d6783/1
create or replace FUNCTION sinceHumanReadable(start_date IN date,end_date IN date)
RETURN VARCHAR2
IS result VARCHAR2(255);
BEGIN
with
dates_parts as (
SELECT
EXTRACT(YEAR FROM (end_date - start_date) YEAR TO MONTH ) as Year,
EXTRACT(MONTH FROM (end_date - start_date) YEAR TO MONTH ) as Month,
EXTRACT(HOUR FROM cast(end_date as timestamp) - cast(start_date as timestamp)) as Hour,
EXTRACT(MINUTE FROM cast(end_date as timestamp) - cast(start_date as timestamp)) as Minute,
EXTRACT(SECOND FROM cast(end_date as timestamp) - cast(start_date as timestamp)) as Second
FROM dual
),
dates_parts_with_day as (
select
Year,Month,Hour,Minute,Second,
EXTRACT(DAY FROM (end_date - ADD_MONTHS(start_date,Month+Year*12)) DAY TO SECOND ) as Day
from dates_parts
)
select
decode(dates_parts_with_day.Year, 0,'', dates_parts_with_day.Year || ' years ' )||
decode(dates_parts_with_day.Month,0,'', dates_parts_with_day.Month || ' months ')||
decode(dates_parts_with_day.Day,0,'', dates_parts_with_day.Day || ' days ')||
decode(dates_parts_with_day.Hour,0,'', dates_parts_with_day.Hour || ' hours ')||
decode(dates_parts_with_day.Minute,0,'', dates_parts_with_day.Minute || ' minutes ')||
dates_parts_with_day.Second || ' seconds'
into result
from
dates_parts_with_day;
RETURN(result);
END sinceHumanReadable;
GO
The query
with dates as (
select sysdate-99.90032407407407407407407407407407407407 as d1,sysdate as d2 from dual
union all
select to_date('2016-03-30 17:14:53','yyyy-mm-dd hh24:mi:ss') as d1,to_date('2016-07-08 14:51:21','yyyy-mm-dd hh24:mi:ss') as d2 from dual
union all
select to_date('2014-01-01 12:00:00','yyyy-mm-dd hh24:mi:ss') as d1,to_date('2015-03-04 16:05:06','yyyy-mm-dd hh24:mi:ss') as d2 from dual
union all
select sysdate as d1,add_months(sysdate,35) as d2 from dual
union all
select sysdate as d1,sysdate as d2 from dual
)
select
d1,d2,
sinceHumanReadable(d1,d2) as since
from
dates;
will produce :
| D1 | D2 | SINCE |
|----------------------|----------------------|-----------------------------------------------------|
| 2017-07-19T17:50:00Z | 2017-10-27T15:26:28Z | 3 months 7 days 21 hours 36 minutes 28 seconds |
| 2016-03-30T17:14:53Z | 2016-07-08T14:51:21Z | 3 months 7 days 21 hours 36 minutes 28 seconds |
| 2014-01-01T12:00:00Z | 2015-03-04T16:05:06Z | 1 years 2 months 3 days 4 hours 5 minutes 6 seconds |
| 2017-10-27T15:26:28Z | 2020-09-27T15:26:28Z | 2 years 11 months 0 seconds |
| 2017-10-27T15:26:28Z | 2017-10-27T15:26:28Z | 0 seconds |

SQL - group by issue

I have a table that has records in the form(simplified): ID (int), startTime(DateTime), endTime(DateTime)
I want to be able to group records that "overlap" time duration by minute.
Ex:
1 - 12.00.AM - 12.10.AM ( duration here is 10 min)
2 - 12.05.AM - 12.07.AM (duration here is 2 minutes but is overlapping with record ID = 1 in minutes 05, 06, 07)
The result of such a query should be
minute 12.00 - record 1,
minute 12.01 - record 1,
...
minute 12.05 - record 1 + record 2,
minute 12.06 - record 1 + record 2,
minute 12.07 - record 1 + record 2
...
minute 12.10 - record 1
Note I use sql server (2005 uppwards)
This is one way to do it in Oracle (11g Release 2 as it includes the LISTAGG function):
with CTE as
( select STRT + (rownum - 1) / 24 / 60 as TIMES
from (select min(STARTTIME) as STRT from FORM1)
connect by level <=
(select (max(ENDTIME) - min(STARTTIME)) * 24 * 60
from FORM1))
select to_char(CTE.TIMES, 'hh24:mi') as MINUTE
,LISTAGG(ID, ',') within group (order by ID) as IDS
from CTE
join
FORM1
on CTE.TIMES <= FORM1.ENDTIME and CTE.TIMES >= FORM1.STARTTIME
group by to_char(CTE.TIMES, 'hh24:mi')
order by to_char(CTE.TIMES, 'hh24:mi')
The test data I used was:
create table FORM1
(
ID number
,STARTTIME date
,ENDTIME date
);
insert into FORM1
values (
1
,to_date('26/01/2012 00:00:00', 'dd/mm/yyyy hh24:mi:ss')
,to_date('26/01/2012 00:10:00', 'dd/mm/yyyy hh24:mi:ss'));
insert into FORM1
values (
2
,to_date('26/01/2012 00:05:00', 'dd/mm/yyyy hh24:mi:ss')
,to_date('26/01/2012 00:07:00', 'dd/mm/yyyy hh24:mi:ss'));
And I get the following result:
Minute IDs
00:00 1
00:01 1
00:02 1
00:03 1
00:04 1
00:05 1,2
00:06 1,2
00:07 1,2
00:08 1
00:09 1