Oracle SQL - Grouping based on time within one second - sql

Sample Table:
create table sampledata as
select 78328696 pkid, 12848815 customer_id, to_date('10/19/2022 11:05:38 AM','MM/DD/YYYY HH:MI:SS AM') actdate, 0.5 units, to_timestamp('19-OCT-22 11.05.38.947750000 AM') datetime from dual
union all
select 78328697, 12848815, to_date('10/19/2022 11:05:39 AM','MM/DD/YYYY HH:MI:SS AM'), 0.5, to_timestamp('19-OCT-22 11.05.39.024819000 AM') from dual
union all
select 78328698, 12848815, to_date('10/19/2022 11:05:39 AM','MM/DD/YYYY HH:MI:SS AM'), 0.5, to_timestamp('19-OCT-22 11.05.39.050859000 AM') from dual
union all
select 78321196, 12978419, to_date('10/19/2022 9:13:56 AM','MM/DD/YYYY HH:MI:SS AM'), 1, to_timestamp('19-OCT-22 09.13.56.879037000 AM') from dual
union all
select 78321197, 12978419, to_date('10/19/2022 9:13:56 AM','MM/DD/YYYY HH:MI:SS AM'), 1, to_timestamp('19-OCT-22 09.13.56.909837000 AM') from dual
union all
select 78321199, 12978419, to_date('10/19/2022 9:13:56 AM','MM/DD/YYYY HH:MI:SS AM'), 1, to_timestamp('19-OCT-22 09.13.56.931040000 AM') from dual
union all
select 78321200, 12978419, to_date('10/19/2022 9:13:56 AM','MM/DD/YYYY HH:MI:SS AM'), 1, to_timestamp('19-OCT-22 09.13.56.952084000 AM') from dual
union all
select 78321201, 12978419, to_date('10/19/2022 9:13:56 AM','MM/DD/YYYY HH:MI:SS AM'), 1, to_timestamp('19-OCT-22 09.13.56.971703000 AM') from dual
union all
select 78321202, 12978419, to_date('10/19/2022 9:13:56 AM','MM/DD/YYYY HH:MI:SS AM'), 1, to_timestamp('19-OCT-22 09.13.56.993092000 AM') from dual
union all
select 78321203, 12978419, to_date('10/19/2022 9:13:57 AM','MM/DD/YYYY HH:MI:SS AM'), 1, to_timestamp('19-OCT-22 09.13.57.014174000 AM') from dual
union all
select 78330838, 13710675, to_date('10/19/2022 11:44:29 AM','MM/DD/YYYY HH:MI:SS AM'), 0.5, to_timestamp('19-OCT-22 11.44.29.465212000 AM') from dual
union all
select 78330839, 13710675, to_date('10/19/2022 11:44:29 AM','MM/DD/YYYY HH:MI:SS AM'), 0.5, to_timestamp('19-OCT-22 11.44.29.498326000 AM') from dual
union all
select 78330840, 13710675, to_date('10/19/2022 11:44:29 AM','MM/DD/YYYY HH:MI:SS AM'), 0.5, to_timestamp('19-OCT-22 11.44.29.527076000 AM') from dual
union all
select 78331625, 13710675, to_date('10/19/2022 11:56:28 AM','MM/DD/YYYY HH:MI:SS AM'), 0.5, to_timestamp('19-OCT-22 11.56.28.726815000 AM') from dual
I am looking to aggregate transactions together and sum the units. However, they need to be in the same transaction and there is nothing that specifically denotes a transaction. They will all have an ACTDATE within 1 or perhaps 2 seconds. So I am looking to group the first 3 rows together based on CUSTOMER_ID and sum the units. The next 7 rows would be grouped together as one transaction as well. The tricky part is when I hit the last 4 rows, CUSTOMER_ID 13710675. Here, there are actually 2 transactions. One transaction consisting of 3 rows at 11:44 and then a single row transaction at 11:56.
I have considered doing a lead(ACTDATE) over(partition by..., and look at the time difference, but this gets convoluted as well as resource and execution time heavy considering the number of rows in the actual data. I looked at rounding the microseconds in order to get the ACTDATE to match and include it in the GROUP BY, but this sacrifices some accuracy, including in the example given. Can you recommend an easier method? Please notice that the PKID may skip a number. I will take the max(PKID) and the trunc(ACTDATE) so the output should be:

On recent versions of Oracle - since Oracle 12c - you can use match_recognize to do this:
select max(pkid) as pkid,
customer_id,
trunc(max(actdate)) as accdate,
sum(units) as units
from sampledata
match_recognize (
partition by customer_id
order by actdate
measures match_number() as match_num
all rows per match
pattern (start_tran same_tran*)
define same_tran as (actdate <= prev(actdate) + interval '2' second)
)
group by customer_id, match_num
PKID
CUSTOMER_ID
ACCDATE
UNITS
78328698
12848815
10/19/2022
1.5
78321203
12978419
10/19/2022
7
78330840
13710675
10/19/2022
1.5
78331625
13710675
10/19/2022
.5
fiddle
The data is partitioned by customer_id, and for each ID the rows are ordered by actdate. The pattern and definition together define what constitutes a matching set - within a time window in this case - and each of those sets is assigned a match number. The data can then be grouped by the customer ID and that match number, and aggregates can then be calculated.
You can change the interval to make the matching window larger or smaller if necessary. It would work ordering and defining using datetime instead of actdate - not sure why you have both or if one is more appropriate than the other.

Related

find overlap between two sets of date ranges

I would like to find the overlap between two sets of date ranges for every order number using Oracle SQL or PL/SQL.
The inputs are "result set one" and "result set two". The output should be "overlap".
result set one
WITH T_RESULT_SET_ONE as(
select 21365 order_number,to_date('01/01/2021 09:00:00', 'DD/MM/YYYY HH24:MI:SS') start_date_time, to_date('01/01/2021 10:30:00', 'DD/MM/YYYY HH24:MI:SS') finish_date_time FROM DUAL
UNION
select 21365 order_number,to_date('02/01/2021 14:00:00', 'DD/MM/YYYY HH24:MI:SS') start_date_time, to_date('02/01/2021 18:00:00', 'DD/MM/YYYY HH24:MI:SS') finish_date_time FROM DUAL
UNION
select 21367 order_number,to_date('01/01/2021 08:00:00', 'DD/MM/YYYY HH24:MI:SS') start_date_time, to_date('01/01/2021 09:43:00', 'DD/MM/YYYY HH24:MI:SS') finish_date_time FROM DUAL
UNION
select 21367 order_number,to_date('01/01/2021 16:34:00', 'DD/MM/YYYY HH24:MI:SS') start_date_time, to_date('01/01/2021 18:15:00', 'DD/MM/YYYY HH24:MI:SS') finish_date_time FROM DUAL
union
select 21367 order_number,to_date('04/01/2021 15:00:00', 'DD/MM/YYYY HH24:MI:SS') start_date_time, to_date('04/01/2021 16:15:00', 'DD/MM/YYYY HH24:MI:SS') finish_date_time FROM DUAL
)
or
result set two
T_RESULT_SET_TWO as(
select 21365 order_number,to_date('01/01/2021 09:30:00', 'DD/MM/YYYY HH24:MI:SS') start_date_time, to_date('01/01/2021 09:45:00', 'DD/MM/YYYY HH24:MI:SS') finish_date_time FROM DUAL
UNION
select 21365 order_number,to_date('02/01/2021 13:00:00', 'DD/MM/YYYY HH24:MI:SS') start_date_time, to_date('02/01/2021 17:00:00', 'DD/MM/YYYY HH24:MI:SS') finish_date_time FROM DUAL
union
select 21367 order_number,to_date('01/01/2021 09:00:00', 'DD/MM/YYYY HH24:MI:SS') start_date_time, to_date('01/01/2021 10:00:00', 'DD/MM/YYYY HH24:MI:SS') finish_date_time FROM DUAL
UNION
select 21367 order_number,to_date('01/01/2021 16:00:00', 'DD/MM/YYYY HH24:MI:SS') start_date_time, to_date('01/01/2021 19:00:00', 'DD/MM/YYYY HH24:MI:SS') finish_date_time FROM DUAL
UNION
select 21367 order_number,to_date('05/01/2021 19:00:00', 'DD/MM/YYYY HH24:MI:SS') start_date_time, to_date('04/01/2021 19:46:00', 'DD/MM/YYYY HH24:MI:SS') finish_date_time FROM DUAL
)
or
overlap
T_OVERLAP as
(
select 21365 order_number,to_date('01/01/2021 09:30:00', 'DD/MM/YYYY HH24:MI:SS') start_date_time, to_date('01/01/2021 09:45:00', 'DD/MM/YYYY HH24:MI:SS') finish_date_time FROM DUAL
union
select 21365 order_number,to_date('02/01/2021 14:00:00', 'DD/MM/YYYY HH24:MI:SS') start_date_time, to_date('02/01/2021 17:00:00', 'DD/MM/YYYY HH24:MI:SS') finish_date_time FROM DUAL
union
select 21367 order_number,to_date('01/01/2021 09:00:00', 'DD/MM/YYYY HH24:MI:SS') start_date_time, to_date('01/01/2021 09:43:00', 'DD/MM/YYYY HH24:MI:SS') finish_date_time FROM DUAL
UNION
select 21367 order_number,to_date('01/01/2021 16:34:00', 'DD/MM/YYYY HH24:MI:SS') start_date_time, to_date('01/01/2021 18:15:00', 'DD/MM/YYYY HH24:MI:SS') finish_date_time FROM DUAL
)
or
The following image ilustrates the operation I am trying to execute (the date ranges are not the same as the ones I provided earlier)
Could anyone provide a SQL query or PL/SQL program that does that?
It seems what you are looking for is not INTERSECT but overlaps. In Oracle intersect generally refers to the common result of 2 queries:
Select <columns list> from table1
INTERSECT
Select <columns list> from table2;
Where the column lists have the same definition and the resulting values are the same. What you are looking for is where the values overlap one another each other not where the rows contain same values.
Lets consider 2 events call then 'A' and 'B', there are 4 possibilities for overlap:
A starts, B starts, B ends, A ends. A completely overlaps B.
A starts, B starts, A ends, B ends. A overlaps beginning of B
B starts, A starts, B ends, A ends. A overlaps ending of B
B starts, A starts, A ends, B ends. A is completely overlap by B.
Resolving is just determining is needs to determine the overlap we take the greatest start time and the least end time. With the data you provided this requires just one of the above:
select order_number
, greatest(t1start, t2start) start_date_time
, least(t1finish,t2finish) finish_date_time
from ( select t1.order_number
, t1.start_date_time t1start
, t1.finish_date_time t1finish
, t2.start_date_time t2start
, t2.finish_date_time t2finish
from t_result_set_one t1
join t_result_set_two t2
on t1.order_number = t2.order_number
where ( t1.finish_date_time >= t2.start_date_time
and t1.start_date_time <= t2.finish_date_time
)
);
See fiddle here. I leave the other 3 possibilities for you.

How to compare date with time in oracle

Trying to compare two dates with time.But comparison is not working.
SELECT *
FROM attendance
WHERE TO_DATE (checktime, 'DD/MM/YYYY HH:MI:SS AM') >=
TO_DATE ('01/09/2019 04:30:00 PM', 'DD/MM/YYYY HH:MI:SS AM')
AND TO_DATE (checktime, 'DD/MM/YYYY HH:MI:SS AM') <=
TO_DATE ('30/09/2019 10:00:00 PM', 'DD/MM/YYYY HH:MI:SS AM')
AND userid = '3825'
AND SUBSTR (checktime, -2, 2) = 'PM'
ORDER BY TO_DATE (checktime, 'DD/MM/YYYY HH:MI:SS AM') ASC
I was expecting output equal or grater then 04:30 PM and less then or equal 10:00 PM.But this date comparison is not working.Here is the
Output of Code.I want my result includes date and time between mentioned periods.
Note:CHECKTIME datatype is varchar2.
I think you need data for all the days(01/09/2019 - 30/09/2019) and the time of the day should be between 04:30 PM and 10: PM.
You can achieve this using the following query:
SELECT
*
FROM
ATTENDANCE
WHERE
TRUNC(TO_DATE(CHECKTIME, 'DD/MM/YYYY HH:MI:SS AM'))
BETWEEN TO_DATE('01/09/2019', 'DD/MM/YYYY')
AND TO_DATE('30/09/2019', 'DD/MM/YYYY')
AND ( TO_DATE(CHECKTIME, 'DD/MM/YYYY HH:MI:SS AM') - TRUNC(TO_DATE(CHECKTIME, 'DD/MM/YYYY HH:MI:SS AM')) ) * 1440 -- converting difference into minutes
BETWEEN 990 -- 04:30 PM in minutes (16.5*60)
AND 1320 -- 10:00 PM in minutes (22*60)
AND USERID = '3825'
AND SUBSTR(CHECKTIME, - 2, 2) = 'PM'
ORDER BY
TO_DATE(CHECKTIME, 'DD/MM/YYYY HH:MI:SS AM') ASC;
Cheers!!
You can fix your format by using such a format containing TO_TIMESTAMP conversion
SELECT *
FROM attendance
WHERE checktime
BETWEEN TO_TIMESTAMP('01/09/2019 16:30:00.000000','dd/mm/yyyy hh24:mi:ss.ff')
AND TO_TIMESTAMP('30/09/2019 22:30:00.000000','dd/mm/yyyy hh24:mi:ss.ff')
AND userid = 3825
ORDER BY checktime;
Demo
EDIT : you had better to add a new column with timestamp datatype, and update your new column's data by using TO_TIMESTAMP conversion such as below :
UPDATE attendance
SET checktime2 = TO_TIMESTAMP('3/09/2019 5:38:36 PM','dd/mm/yyyy hh:mi:ss AM')
WHERE id = 3825
AND checktime = '3/09/2019 5:38:36 PM'

Union times in sql oracle - difficult problem

I have a big table with start times and end times.
It looks like this:
Start_time date-time (format: dd/mm/yyyy hh24:mi:ss),
End_time date-time (format: dd/mm/yyyy hh24:mi:ss)
I might have rows that represent time which is included in other rows
My desiarable result is a table that can solve this containing. I want take any firsy time and see next to him the last end time.
I tried to left join the table with itself on start time between start time and end time if the end of the second is greater than the ending of the first. Then to do a sliding window and take the max end time with sliding window or even with group by.
However, this idea does take in account then I may have, for example:
10:05-10:10
10:07-10:12
10:09-10:15
10:11-10:20
So when I am joined I allegedly get 10:05-10:15 and 10:11-10:20. The row of 10:11 is not joined to the first row because it is not included in that time.
I have here again the same problem I had in the begining.
My desiarable result is actually for the rows above:
10:05-10:20
Seem to be a difficult problem.
I dont know plsql but thought maybe about doing some function that repeat this query until it has nothing to join?
Hope to get ypur help!
Thanks.
I dont know how to format, but you can copy paste in your editor and than format.
Insted of my test data with operator "with" you may use your table.
I suppose you have some sort of ID so i include it:
with test_table as (
select 1 id, to_date('2019-04-07 10:05', 'yyyy-mm-dd hh24:mi') start_time, to_date('2019-04-07 10:08', 'yyyy-mm-dd hh24:mi') end_time from dual union all
select 2 id, to_date('2019-04-07 10:07', 'yyyy-mm-dd hh24:mi') start_time, to_date('2019-04-07 10:10', 'yyyy-mm-dd hh24:mi') end_time from dual union all
select 3 id, to_date('2019-04-07 10:11', 'yyyy-mm-dd hh24:mi') start_time, to_date('2019-04-07 10:15', 'yyyy-mm-dd hh24:mi') end_time from dual union all
select 4 id, to_date('2019-04-07 10:12', 'yyyy-mm-dd hh24:mi') start_time, to_date('2019-04-07 10:20', 'yyyy-mm-dd hh24:mi') end_time from dual union all
select 5 id, to_date('2019-04-08 10:05', 'yyyy-mm-dd hh24:mi') start_time, to_date('2019-04-08 10:10', 'yyyy-mm-dd hh24:mi') end_time from dual union all
select 6 id, to_date('2019-04-08 10:07', 'yyyy-mm-dd hh24:mi') start_time, to_date('2019-04-08 10:12', 'yyyy-mm-dd hh24:mi') end_time from dual union all
select 7 id, to_date('2019-04-08 10:09', 'yyyy-mm-dd hh24:mi') start_time, to_date('2019-04-08 10:15', 'yyyy-mm-dd hh24:mi') end_time from dual union all
select 8 id, to_date('2019-04-08 10:11', 'yyyy-mm-dd hh24:mi') start_time, to_date('2019-04-08 10:20', 'yyyy-mm-dd hh24:mi') end_time from dual
)
select id, to_char(start_time, 'yyyy-mm-dd hh24:mi') start_time, to_char(end_time, 'yyyy-mm-dd hh24:mi') end_time,
(SELECT MAX(to_char(end_time, 'yyyy-mm-dd hh24:mi'))
from test_table t2
connect by nocycle
prior t2.id != t2.id and
PRIOR end_time > start_time and
PRIOR start_time < end_time
start with t2.id = t1.id) max_date
from test_table t1;

Oracle query to get data between specific time

We are trying to find out the transactions made in between 00 to 03 AM for WHOLE YEAR.
Tried extract and to_date functions with many modification but unable to get the exact result.
Below Gives data just for current month even the date is not specified
and TXNDATE >= to_date('00:00:00', 'HH24:MI:SS')
and TXNDATE <= to_date('03:30:00','HH24:MI:SS')
Please assist.
How about just extracting the hour?
and extract(hour from TXNDATE) < 3
If you want to handle the time directly, strings might be the best approach:
to_char(txndate, 'HH24:MI:SS') >= '00:00:00' and
to_char(txndate, 'HH24:MI:SS') < '03:00:00'
If performance is an issue and you want to use an index, you can create an index incorporating to_char(txndate, 'HH24:MI:SS').
Try this:
WHERE to_char(tnxdate, 'HH24:MI:SS') between '00:00:00' and '03:30:00'
You can do this using 'EXTRACT' but you have to be careful about coding the edge conditions correctly. Here's an example:
WITH cteTxndates AS (SELECT TO_DATE('01-JAN-2018 00:00:00', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL UNION ALL -- *
SELECT TO_DATE('01-JAN-2018 00:00:01', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL UNION ALL -- *
SELECT TO_DATE('05-FEB-2018 00:01:01', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL UNION ALL -- *
SELECT TO_DATE('10-MAR-2018 00:10:01', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL UNION ALL -- *
SELECT TO_DATE('15-MAR-2018 01:00:01', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL UNION ALL -- *
SELECT TO_DATE('20-APR-2018 10:00:01', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL UNION ALL --
SELECT TO_DATE('25-MAY-2018 02:00:01', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL UNION ALL -- *
SELECT TO_DATE('30-JUN-2018 03:00:01', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL UNION ALL -- *
SELECT TO_DATE('02-JUL-2018 10:00:01', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL UNION ALL --
SELECT TO_DATE('20-AUG-2018 01:00:01', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL UNION ALL -- *
SELECT TO_DATE('03-SEP-2018 02:30:01', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL UNION ALL -- *
SELECT TO_DATE('30-OCT-2018 03:30:00', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL UNION ALL -- *
SELECT TO_DATE('30-OCT-2018 03:30:01', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL UNION ALL --
SELECT TO_DATE('04-NOV-2018 02:22:01', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL UNION ALL -- *
SELECT TO_DATE('14-DEC-2018 01:11:01', 'DD-MON-YYYY HH24:MI:SS') AS TXNDATE FROM DUAL), -- *
cteDatefields AS (SELECT TXNDATE,
EXTRACT(HOUR FROM CAST(TXNDATE AS TIMESTAMP)) AS XHOUR,
EXTRACT(MINUTE FROM CAST(TXNDATE AS TIMESTAMP)) AS XMINUTE,
EXTRACT(SECOND FROM CAST(TXNDATE AS TIMESTAMP)) AS XSECOND
FROM cteTxndates)
SELECT *
FROM cteDatefields
WHERE XHOUR >= 0 AND
( XHOUR < 3 OR
(XHOUR = 3 AND XMINUTE < 30) OR
(XHOUR = 3 AND XMINUTE = 30 AND XSECOND = 0));
dbfiddle here

Calculate time difference ignorning non-work hours

I am trying to calculate the difference between 2 datetime values where non-work hours are ignored. Originally it just looked at the difference and calculated it as minutes however It needs to count only hours between 9am and 8pm Monday to Friday and 9am - 1pm Saturday, ignoring all other times. I am on an oracle 10g system.
my code as it currently stands is as follows:
begin
debug.debug('sp_access');
update cl_case b
set time_to_sp_access =
(
select (x.date_created-e.date_created)*1440
from cl_case c, eventlog e, eventlog x
where c.id=e.case_id
and x.case_id=e.case_id
and b.id=e.case_id
and e.id=
( select min(id) from eventlog mini
where mini.case_id=e.case_id
and mini.cl_code in ('AAAA','BBBB','CCCC','DDDD')
)
and x.id=
( select min(id) from eventlog minix
where minix.case_id=e.case_id
and minix.cl_code in ('EEEE','FFF','GGG','HHHH','JJJJ','KKKK','LLLL')
)
)
where id in
( select unique case_id
from eventlog elog
where elog.sptime_needs_setting ='Y'
);
commit;
end sp_access;
How can I get this to count time between specified hours?
thanks
You could use a CASE expression in the WHERE clause. Since there are two datetime values, you need to use two case expressions.
For example, the CASE expression would evaluate as:
SQL> SELECT
2 CASE
3 WHEN TO_CHAR(SYSDATE, 'DY') BETWEEN '1' AND '5'
4 THEN TO_DATE(TO_CHAR(TRUNC(SYSDATE), 'MM/DD/YYYY')
5 ||' 08:00:00 PM', 'MM/DD/YYYY HH:MI:SS PM')
6 ELSE TO_DATE(TO_CHAR(TRUNC(SYSDATE), 'MM/DD/YYYY')
7 ||' 01:00:00 PM', 'MM/DD/YYYY HH:MI:SS PM')
8 END my_time
9 FROM dual;
MY_TIME
----------------------
11/24/2015 01:00:00 pm
The above example check the DAY for SYSDATE, and depending on it returns a datetime value.
using the above example, since you have two different datetime values to be compared as a date range condition, you will need two CASE expressions in your WHERE clause.
WHERE date_column
BETWEEN
CASE
WHEN TO_CHAR(date_column, 'DY') BETWEEN '1' AND '5'
THEN
TO_DATE(TO_CHAR(
TRUNC(date_column), 'MM/DD/YYYY')
||' 09:00:00 AM', 'MM/DD/YYYY HH:MI:SS PM')
ELSE
TO_DATE(TO_CHAR(
TRUNC(date_column), 'MM/DD/YYYY')
||' 09:00:00 AM', 'MM/DD/YYYY HH:MI:SS PM')
END
AND
CASE
WHEN TO_CHAR(date_column, 'DY') BETWEEN '1' AND '5'
THEN
TO_DATE(TO_CHAR(
TRUNC(date_column), 'MM/DD/YYYY')
||' 08:00:00 PM', 'MM/DD/YYYY HH:MI:SS PM')
ELSE
TO_DATE(TO_CHAR(
TRUNC(date_column), 'MM/DD/YYYY')
||' 01:00:00 PM', 'MM/DD/YYYY HH:MI:SS PM')
END