Find periods from timestamps in ordered table - sql

Let's assume I have following table CALLS which is sorted by column CALL of type TIMESTAMP:
CALL TYPE
--------------------- ------
31.10.2018 10:00:00 OFF
31.10.2018 11:00:00 ON
31.10.2018 12:00:00 ON
31.10.2018 13:00:00 ON
31.10.2018 14:00:00 OFF
31.10.2018 15:00:00 OFF
31.10.2018 16:00:00 ON
31.10.2018 17:00:00 ON
I want to write view that will find individual groups of calls with TYPE=ON and extract their start and end dates. As a result, for given example I get two groups:
START END
--------------------- ---------------------
31.10.2018 11:00:00 31.10.2018 13:00:00
31.10.2018 16:00:00 31.10.2018 17:00:00
We should assume:
Minimal count of group is 1, so we can get group that has the same start and end date
ON rows are seperated by OFF rows but the first and the last row don't have to be OFF type
Is it possible to achieve that in Oracle 12c?

This is a gaps-and-islands problem. In this case, a difference of row numbers with aggregation does what you want:
select min(call) as start_time, max(call) as end_time
from (select t.*,
row_number() over (partition by type order by call) as seqnum_t,
row_number() over (order by call) as seqnum
from t
) t
where type = 'ON'
group by (seqnum - seqnum_t)

If you run Oracle 12 then you can use also the SQL for Pattern Matching
Would be like this:
WITH t (CALL, TYPE) AS (
SELECT TO_TIMESTAMP('31.10.2018 10:00:00', 'dd.mm.yyyy hh24:mi:ss'), 'OFF' FROM dual UNION ALL
SELECT TO_TIMESTAMP('31.10.2018 11:00:00', 'dd.mm.yyyy hh24:mi:ss'), 'ON' FROM dual UNION ALL
SELECT TO_TIMESTAMP('31.10.2018 12:00:00', 'dd.mm.yyyy hh24:mi:ss'), 'ON' FROM dual UNION ALL
SELECT TO_TIMESTAMP('31.10.2018 13:00:00', 'dd.mm.yyyy hh24:mi:ss'), 'ON' FROM dual UNION ALL
SELECT TO_TIMESTAMP('31.10.2018 14:00:00', 'dd.mm.yyyy hh24:mi:ss'), 'OFF' FROM dual UNION ALL
SELECT TO_TIMESTAMP('31.10.2018 15:00:00', 'dd.mm.yyyy hh24:mi:ss'), 'OFF' FROM dual UNION ALL
SELECT TO_TIMESTAMP('31.10.2018 16:00:00', 'dd.mm.yyyy hh24:mi:ss'), 'ON' FROM dual UNION ALL
SELECT TO_TIMESTAMP('31.10.2018 17:00:00', 'dd.mm.yyyy hh24:mi:ss'), 'ON' FROM dual)
SELECT *
FROM t
MATCH_RECOGNIZE (
ORDER BY CALL
MEASURES
FINAL MIN(CALL) AS CALL_START,
FINAL MAX(CALL) AS CALL_END
PATTERN ( CALL_ON+ )
DEFINE
CALL_ON AS TYPE = 'ON'
);
+-----------------------------------------------------------+
| CALL_START | CALL_END |
+-----------------------------------------------------------+
| 31.10.2018 11:00:00.000 | 31.10.2018 13:00:00.000 |
| 31.10.2018 16:00:00.000 | 31.10.2018 17:00:00.000 |
+-----------------------------------------------------------+

Related

I have a table containing in_time and out_time in the format 'HH12:MI'(varchar2).I want to calculate the total time in hours between this column

select 24 * ROUND(
to_date(OUT_TIME, 'YYYY-MM-DD hh24:mi')
- to_date(IN_TIME, 'YYYY-MM-DD hh24:mi'),
2
) diff_hours
from attendances
where employee_id = 1001;
select 24 * ROUND(
to_date('2009-07-07 23:44', 'YYYY-MM-DD hh24:mi')
- to_date('2009-07-07 19:30', 'YYYY-MM-DD hh24:mi'),
2
) diff_hours
from dual;
If you stored (date)time values as strings (which is a bad idea), you'll have to use that format with to_date. Using sample data you posted:
SQL> with test (employee, in_time, out_time) as
2 (select 'Palash', '08:45 PM', '08:45 PM' from dual union all
3 select 'Palash', '07:40 PM', '07:45 PM' from dual union all
4 select 'Sagor' , '08:10 PM', '08:43 PM' from dual
5 )
6 select employee, in_time, out_time,
7 round((to_date(out_time, 'hh:mi pm') - to_date(in_time, 'hh:mi pm')) * 24, 2) diff
8 from test;
EMPLOY IN_TIME OUT_TIME DIFF
------ -------- -------- ----------
Palash 08:45 PM 08:45 PM 0
Palash 07:40 PM 07:45 PM .08
Sagor 08:10 PM 08:43 PM .55
SQL>

Oracle SQL - Round down dates to quarter

How can I round down hour data to quarters?
Eg.
| Hour (HH24:MI:SS) | Expected result | Query result |
|--|--|--|
| 10:04:00| 10:00:00|10:00:00|
| 10:12:00| 10:00:00|10:15:00|
| 10:14:59| 10:00:00|10:15:00|
| 10:15:00| 10:15:00|10:15:00|
| 10:15:02| 10:15:00|10:15:00|
| 10:16:00| 10:15:00|10:15:00|
I wrote something like this, but it only rounding to nearest quarter :/
with dates as (
select to_date('01/01/2022 10:04:00', 'mm/dd/yyyy hh:mi:ss') d from dual
union all
select to_date('01/01/2022 10:12:00', 'mm/dd/yyyy hh:mi:ss') d from dual
union all
select to_date('01/01/2022 10:14:59', 'mm/dd/yyyy hh:mi:ss') d from dual
union all
select to_date('01/01/2022 10:15:00', 'mm/dd/yyyy hh:mi:ss') d from dual
union all
select to_date('01/01/2022 10:15:02', 'mm/dd/yyyy hh:mi:ss') d from dual
union all
select to_date('01/01/2022 10:16:00', 'mm/dd/yyyy hh:mi:ss') d from dual
)
select d
,TRUNC (d) + (ROUND ((d - TRUNC (d)) * 96) / 96)
from dates;
You can use the MODulo function to find the number of minutes and seconds to subtract:
with dates as (
select to_date('01/01/2022 10:04:00', 'mm/dd/yyyy hh:mi:ss') d from dual
union all
select to_date('01/01/2022 10:12:00', 'mm/dd/yyyy hh:mi:ss') d from dual
union all
select to_date('01/01/2022 10:14:59', 'mm/dd/yyyy hh:mi:ss') d from dual
union all
select to_date('01/01/2022 10:15:00', 'mm/dd/yyyy hh:mi:ss') d from dual
union all
select to_date('01/01/2022 10:15:02', 'mm/dd/yyyy hh:mi:ss') d from dual
union all
select to_date('01/01/2022 10:16:00', 'mm/dd/yyyy hh:mi:ss') d from dual
)
select d,
d - MOD((d - TRUNC(d))*24*60, 15)/24/60 AS rounded
from dates;
Which outputs:
D
ROUNDED
2022-01-01 10:04:00
2022-01-01 10:00:00
2022-01-01 10:12:00
2022-01-01 10:00:00
2022-01-01 10:14:59
2022-01-01 10:00:00
2022-01-01 10:15:00
2022-01-01 10:15:00
2022-01-01 10:15:02
2022-01-01 10:15:00
2022-01-01 10:16:00
2022-01-01 10:15:00
db<>fiddle here

Oracle find gaps split by allowed slots

I have this table of calendar gaps (for a report):
begindate enddate
2017-12-14 16:45:00 2017-12-14 21:45:00
2017-12-15 17:45:00 2017-12-16 10:00:00
The second line range on 2 different days.
I want to split it with given 'allowed' time slots, gaps can only be between 7am and 8pm (20:00:00).
So the result should be 3 lines:
begindate enddate
2017-12-14 16:45:00 2017-12-14 20:00:00
2017-12-15 17:45:00 2017-12-15 20:00:00
2017-12-16 07:00:00 2017-12-16 10:00:00
How can I do that in sql (oracle function allowed).
This was an interesting one, here is my answer:
WITH test_data (beginDate, endDate) AS
(
SELECT TO_DATE('2017-12-14 16:45:00', 'YYYY-MM-DD HH24:MI:SS'),
TO_DATE('2017-12-14 21:45:00', 'YYYY-MM-DD HH24:MI:SS') FROM DUAL
UNION ALL
SELECT TO_DATE('2017-12-15 17:45:00', 'YYYY-MM-DD HH24:MI:SS'),
TO_DATE('2017-12-16 10:00:00', 'YYYY-MM-DD HH24:MI:SS') FROM DUAL
UNION ALL
SELECT TO_DATE('2017-12-15 01:45:00', 'YYYY-MM-DD HH24:MI:SS'),
TO_DATE('2017-12-15 06:00:00', 'YYYY-MM-DD HH24:MI:SS') FROM DUAL
),
split_dates (beginDate, endDate, actEnd, remaining, lvl) AS
(
SELECT beginDate, LEAST(endDate, TRUNC(beginDate)+1), endDate, TRUNC(endDate) - TRUNC(beginDate), 1
FROM test_data
UNION ALL
SELECT TRUNC(beginDate)+lvl, LEAST(actEnd, TRUNC(beginDate)+lvl+1), actEnd, remaining-1, lvl+1
FROM split_dates sd
WHERE sd.remaining > 0
)
SELECT TO_CHAR(GREATEST(sd.beginDate, TRUNC(sd.beginDate)+7/24), 'YYYY-MM-DD HH24:MI:SS') AS beginDate,
TO_CHAR(LEAST(sd.endDate, TRUNC(sd.beginDate)+5/6), 'YYYY-MM-DD HH24:MI:SS') AS endDate
FROM split_dates sd
WHERE GREATEST(sd.beginDate, TRUNC(sd.beginDate)+7/24) <= LEAST(sd.endDate, TRUNC(sd.beginDate)+5/6);
The problem is two-fold:
You need to split multi-day records in separate rows. I accomplished this with the split_records CTE.
You need to overlay your valid times on the calculated splits and check that the new times are valid.
I created a DBFiddle to show you the query in action (Link)

Oracle - Max value from each date with date/time

I have table with column Date with timestamp, value in seconds and have query like
Table
2017-01-10 06:45:00 PM 1119
2017-01-10 03:30:00 PM 1054
2017-01-11 11:15:00 PM 379
2017-01-10 06:30:00 PM 377
2017-01-11 09:15:00 PM 375
Query
SELECT
TO_char(DtTm,'YYYY-MM-DD hh:mi:ss AM') As DataDt,
max(MaxSec) as Wait_sec, DtTimeTable.HrID,
FROM DtTimeTable
WHERE DtTimeTable.HrName in ('Dept1', 'Dept2', 'Dept3')
AND DtTm BETWEEN to_date('2017-01-08 00:00:00', 'YYYY-MM-DD hh24:mi:ss')
AND to_date('2017-01-10 23:59:59', 'YYYY-MM-DD hh24:mi:ss')
Group by TO_char(DtTm,'YYYY-MM-DD hh:mi:ss AM'),DtTimeTable.HrID
order by Wait_sec desc
This gives me All records and if i add
select * from
Query1 --(above)
where rownum <1 order by Wait_sec desc, Datadt desc;
I am only getting highest value of result set
How can get DatewithTime, Maxvalue for each date like
2017-01-10 06:45:00 PM 1119
2017-01-11 11:15:00 PM 379
Try something like this:
Select *
From (
Select t.*,
Row_number() over(partition by trunc(datecol) order by value desc nulls last) rn
From yourtable t
) where rn = 1;
It assign row number within date based on descending order your value column and then filters to get the first row
The solution below uses grouping, the MAX() aggregate function, and the FIRST/LAST function (with KEEP DENSE_RANK). If for a date the same highest value is reached more than once, it picks the first time during the day that the value was reached.
with
test_data( dt, val ) as (
select to_date('2017-01-10 06:45:00 PM', 'yyyy-mm-dd hh:mi:ss AM'), 1119 from dual
union all
select to_date('2017-01-10 03:30:00 PM', 'yyyy-mm-dd hh:mi:ss AM'), 1054 from dual
union all
select to_date('2017-01-11 11:15:00 PM', 'yyyy-mm-dd hh:mi:ss AM'), 379 from dual
union all
select to_date('2017-01-10 06:30:00 PM', 'yyyy-mm-dd hh:mi:ss AM'), 377 from dual
union all
select to_date('2017-01-11 09:15:00 PM', 'yyyy-mm-dd hh:mi:ss AM'), 375 from dual
)
-- end of test data; SQL query begins below this line (use actual table and column names)
select min(dt) keep(dense_rank last order by val) as dt, max(val) as val
from test_data
group by trunc(dt)
order by dt -- if needed
;
DT VAL
---------------------- ----------
2017-01-10 06:45:00 PM 1119
2017-01-11 11:15:00 PM 379
2 rows selected.

Find the difference between two dates in hours and minutes

I am trying to come up with a way to calculate the difference between two dates in hours and minutes.
I have a table with two columns Start Date and TimeStamp:
Start Date Timestamp
-------------------- --------------------
05/JAN/2016 05:30:00 01/JAN/2016 10:02:29
30/JAN/2016 06:10:00 18/JAN/2016 19:24:00
23/JAN/2016 06:10:00 08/JAN/2016 10:46:00
05/JAN/2016 05:30:00 30/DEC/2015 16:07:00
23/JAN/2016 06:10:00 08/JAN/2016 12:18:05
01/JAN/2016 14:10:00 16/DEC/2015 16:36:56
01/JAN/2016 14:10:00 16/DEC/2015 11:41:00
03/JAN/2016 05:15:00 02/JAN/2016 11:23:15
03/JAN/2016 05:15:00 02/JAN/2016 07:52:00
I use the query:
select ROUND(RM_LIVE.CRWGNDACTTIME.GNDACTSTARTRM_LIVE.TRANSACTIONLOG.TIMESTAMP,2)
AS "Difference"
from Transaction;
The query result is:
0.002721428571428571428571428571428571428571
0.008178571428571428571428571428571428571429
0.0105785714285714285714285714285714285714
0.003971428571428571428571428571428571428571
Expected result:
133:23
91:28
355:24
353:52
274:46
I got that expected result in Excel using this formula:
= MAX(T982+U982,W982+V982) - MIN(T982+U982,W982+V982)
How can I get the same result in Oracle SQL?
CASE
WHEN trunc(24 * abs(RM_LIVE.TRANSACTIONLOG.TIMESTAMP
- RM_LIVE.CRWGNDACTTIME.GNDACTSTART))
||':'|| lpad(round(60 * mod(24 * abs(RM_LIVE.TRANSACTIONLOG.TIMESTAMP
- RM_LIVE.CRWGNDACTTIME.GNDACTSTART), 1)), 2, '0') <= '11:00' THEN 'LESS'
ELSE 'MORE'
END AS "mORE/LESS",
386:29 1055 01-JAN-16 16-DEC-15 MORE
**102:41 1055 08-NOV-15 04-NOV-15 LESS**
381:33 1055 01-JAN-16 16-DEC-15 MORE
176:45 1055 20-NOV-15 12-NOV-15 MORE
**119:54 1055 08-NOV-15 03-NOV-15 LESS**
I've shown a couple of variations with explanations in this answer, but it seems to be doing slightly more than you want - you don't want to see the seconds - and doesn't allow more than 100 hours.
The simplest way to get the output you want is with:
trunc(24 * (RM_LIVE.CRWGNDACTTIME.GNDACTSTART
- RM_LIVE.TRANSACTIONLOG.TIMESTAMP))
||':'|| lpad(round(60 * mod(24 * (RM_LIVE.CRWGNDACTTIME.GNDACTSTART
- RM_LIVE.TRANSACTIONLOG.TIMESTAMP), 1)), 2, '0')
as difference
The first part gets the whole number of hours, which is similar to a method you added in a comment, but truncating instead of rounding to only get the whole hours. Then there's a colon separator. Then the minutes are calculated by getting the remainder from the hours calculation - via mod() - which is the fractional number of hours, and multiplying that by 60. The lpad() adds a leading zero to the number of minutes, but you coudl use to_char() instead.
If you have a mix of ranges where timestamp could be before or after the start time then you can use the abs() function to always get a positive result.
trunc(24 * abs(RM_LIVE.CRWGNDACTTIME.GNDACTSTART
- RM_LIVE.TRANSACTIONLOG.TIMESTAMP))
||':'|| lpad(round(60 * mod(24 * abs(RM_LIVE.CRWGNDACTTIME.GNDACTSTART
- RM_LIVE.TRANSACTIONLOG.TIMESTAMP), 1)), 2, '0')
as difference
As a demo with your data mocked up in a single table:
create table your_table(id, start_time, timestamp) as
select 1, to_date ('05/JAN/2016 05:30:00', 'DD/MON/YYYY HH24:MI:SS'), to_date('01/JAN/2016 10:02:29', 'DD/MON/YYYY HH24:MI:SS') from dual
union all select 2, to_date ('30/JAN/2016 06:10:00', 'DD/MON/YYYY HH24:MI:SS'), to_date('18/JAN/2016 19:24:00', 'DD/MON/YYYY HH24:MI:SS') from dual
union all select 3, to_date ('23/JAN/2016 06:10:00', 'DD/MON/YYYY HH24:MI:SS'), to_date('08/JAN/2016 10:46:00', 'DD/MON/YYYY HH24:MI:SS') from dual
union all select 4, to_date ('05/JAN/2016 05:30:00', 'DD/MON/YYYY HH24:MI:SS'), to_date('30/DEC/2015 16:07:00', 'DD/MON/YYYY HH24:MI:SS') from dual
union all select 5, to_date ('23/JAN/2016 06:10:00', 'DD/MON/YYYY HH24:MI:SS'), to_date('08/JAN/2016 12:18:05', 'DD/MON/YYYY HH24:MI:SS') from dual
union all select 6, to_date ('01/JAN/2016 14:10:00', 'DD/MON/YYYY HH24:MI:SS'), to_date('16/DEC/2015 16:36:56', 'DD/MON/YYYY HH24:MI:SS') from dual
union all select 7, to_date ('01/JAN/2016 14:10:00', 'DD/MON/YYYY HH24:MI:SS'), to_date('16/DEC/2015 11:41:00', 'DD/MON/YYYY HH24:MI:SS') from dual
union all select 8, to_date ('03/JAN/2016 05:15:00', 'DD/MON/YYYY HH24:MI:SS'), to_date('02/JAN/2016 11:23:15', 'DD/MON/YYYY HH24:MI:SS') from dual
union all select 9, to_date ('03/JAN/2016 05:15:00', 'DD/MON/YYYY HH24:MI:SS'), to_date('02/JAN/2016 07:52:00', 'DD/MON/YYYY HH24:MI:SS') from dual
union all select 10, to_date ('16/JAN/2016 11:15:00', 'DD/MON/YYYY HH24:MI:SS'), to_date('16/JAN/2016 12:44:00', 'DD/MON/YYYY HH24:MI:SS') from dual
union all select 11, to_date ('16/JAN/2016 11:15:00', 'DD/MON/YYYY HH24:MI:SS'), to_date('16/JAN/2016 12:50:00', 'DD/MON/YYYY HH24:MI:SS') from dual;
The equivalent query:
select start_time, timestamp, trunc(24 * abs(start_time - timestamp))
||':'|| lpad(round(60 * mod(24 * abs(start_time - timestamp), 1)), 2, '0')
as difference
from your_table
order by id;
START_TIME TIMESTAMP DIFFERENCE
------------------- ------------------- ----------
2016-01-05 05:30:00 2016-01-01 10:02:29 91:28
2016-01-30 06:10:00 2016-01-18 19:24:00 274:46
2016-01-23 06:10:00 2016-01-08 10:46:00 355:24
2016-01-05 05:30:00 2015-12-30 16:07:00 133:23
2016-01-23 06:10:00 2016-01-08 12:18:05 353:52
2016-01-01 14:10:00 2015-12-16 16:36:56 381:33
2016-01-01 14:10:00 2015-12-16 11:41:00 386:29
2016-01-03 05:15:00 2016-01-02 11:23:15 17:52
2016-01-03 05:15:00 2016-01-02 07:52:00 21:23
2016-01-16 11:15:00 2016-01-16 12:44:00 1:29
2016-01-16 11:15:00 2016-01-16 12:50:00 1:35
You can't easily compare the string value you want - and it has to be a string with a value like 91:28 - with anything else because string comparison of numbers doesn't work well. As you've see, comparing '119:54' with '11:00' is effectively comparing the third character of each string since the first two are the same, so 9 with :.
It would be simpler to leave it as a decimal fraction for comparison:
CASE
WHEN round(24 * abs(RM_LIVE.TRANSACTIONLOG.TIMESTAMP
- RM_LIVE.CRWGNDACTTIME.GNDACTSTART), 2) <= 11 THEN 'LESS"
ELSE 'MORE'
END AS "mORE/LESS",
For the 91:28 example, that will compare the decimal fraction version 91.46 instead; and for 119:54 will compare 119.9, which is more than 11; 102:41 will be compared as 102.68, which is also more than 11.
Or you could simplify it slightly by dividing the fixed value by 24 (hours in a day) instead of multiplying the time difference:
CASE
WHEN abs(RM_LIVE.TRANSACTIONLOG.TIMESTAMP
- RM_LIVE.CRWGNDACTTIME.GNDACTSTART) <= 11/24 THEN 'LESS"
ELSE 'MORE'
END AS "mORE/LESS",