oracle sql query time periods union of set - sql

i have a oracle database that has the following tables.How can i count the usage time of each room?
Time periods may overlap for each room,
The table structure is as follows。
t_room_electricity
+------------+--------------------+--------------------+
| roomcode | starttime | endtime |
+------------+--------------------+--------------------+
| 123 | 2019/5/10 10:00:00 | 2019/5/10 11:30:00 |
| 123 | 2019/5/10 10:30:00 | 2019/5/10 11:00:00 |
| 456 | 2019/5/10 11:00:00 | 2019/5/10 12:00:00 |
| 456 | 2019/5/10 13:00:00 | 2019/5/10 14:00:00 |
| 456 | 2019/5/10 13:30:00 | 2019/5/10 15:00:00 |
| 789 | 2019/6/10 14:22:00 | 2019/6/10 14:26:00 |
| 789 | 2019/6/10 14:31:00 | 2019/6/10 14:36:00 |
| 886 | 2019/6/10 14:32:00 | 2019/6/10 14:35:00 |
+------------+--------------------+--------------------+

Updating Answer to cater to the cases mentioned by OP.
USING MATCH_RECOGNIZE
with data
as (
select *
from t_room_electricity
match_recognize(
partition by roomcode
order by starttime
measures
first(starttime) f_starttime
,last(starttime) l_starttime
,first(endtime) f_endtime
,last(endtime) l_endtime
,min(starttime) as min_starttime
,max(endtime) as max_endtime
,match_number() as mn
,classifier() as cls
pattern(strt group1*)
define group1
as starttime<first(endtime)
)
)
select roomcode
,round(sum((max_endtime-min_starttime)*24*60)) as diff_in_minutes
from data
group by roomcode
See Output
https://dbfiddle.uk/?rdbms=oracle_18&fiddle=783140ede5dfbf8088a6ce77232ecef7
USING SQL
select x.roomcode
,count(distinct x.minutes_worked)
from (
select a.roomcode
,a.starttime
,a.endtime
,a.starttime + lvl/24/60 as minutes_worked
from t_room_electricity a
join (select level as lvl
from dual
connect by level<=24*60
)b
on b.lvl<=to_number((a.endtime-a.starttime)*24*60)
)x
group by x.roomcode
See output..
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=2add9344f1335376b2fe32df21f769d6
Updated Answer to cater to overlaps, (based on a new recordset)
456 | 2019/5/10 11:00:00 | 2019/5/10 12:00:00 |
456 | 2019/5/10 13:00:00 | 2019/5/10 14:00:00 |
456 | 2019/5/10 13:30:00 | 2019/5/10 15:00:00 |
456 | 2019/5/10 13:45:00 | 2019/5/10 15:05:00 |
Considering overlaps the following query can achieve the intended result
select roomcode
,sum(round(time_in_minutes))
from (
select roomcode
,eventdate
,case when start_flag=1
and (lag(start_flag) over(partition by roomcode order by eventdate asc) = 0
or lag(start_flag) over(partition by roomcode order by eventdate asc) is null)
then null
else (eventdate
-
lag(eventdate) over(partition by roomcode order by eventdate asc)
)*24*60
end as time_in_minutes
from (
select roomcode,starttime as eventdate,1 as start_flag from t_room_electricity
union all
select roomcode,endtime as eventdate,0 as start_flag from t_room_electricity
)x
)y
group by roomcode
+----------+-----------------------------+
| ROOMCODE | SUM(ROUND(TIME_IN_MINUTES)) |
+----------+-----------------------------+
| 123 | 90 |
| 456 | 185 |
| 789 | 9 |
| 886 | 3 |
+----------+-----------------------------+
This first creates an inner block which stores each eventdatetime into a single column called EventDate.
Then the step is to check if the previous event was a closed boundary(ie start_flag=1) if it is then it begins counting, other wise it continues computing the difference between the previous event in minutes
After this the results are grouped by roomcode and the time_in_minutes is summed up
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=650b4f0ad7304d7f44e7fabbca160a90
Old Answer
You can achieve this by using a group by expression as follows
select roomcode,sum((endtime-starttime)*24*60) as diff_in_minutes
from t_room_electricity
group by roomcode
+----------+-----------------+
| ROOMCODE | DIFF_IN_MINUTES |
+----------+-----------------+
| 123 | 120 |
| 789 | 9 |
| 456 | 210 |
| 886 | 3 |
+----------+-----------------+
See dbfiddle link.
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=16fd05220157fd274cf0fab4e61c8802

Work with two tables like room:
with rooms AS
(SELECT 123 as room, to_date('2019/05/10 10:00:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/05/10 11:30:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual
UNION ALL
SELECT 123 as room, to_date('2019/05/10 10:30:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/05/10 11:00:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual
UNION ALL
SELECT 456 as room, to_date('2019/05/10 11:00:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/05/10 12:00:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual
UNION ALL
SELECT 456 as room, to_date('2019/05/10 13:00:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/05/10 14:00:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual
UNION ALL
SELECT 456 as room, to_date('2019/05/10 13:30:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/05/10 15:00:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual
UNION ALL
SELECT 789 as room, to_date('2019/06/10 14:22:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/06/10 14:26:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual
UNION ALL
SELECT 789 as room, to_date('2019/06/10 14:31:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/06/10 14:36:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual
UNION ALL
SELECT 886 as room, to_date('2019/06/10 14:32:00', 'yyyy/mm/dd hh24:mi:ss') starttime, to_date('2019/06/10 14:35:00', 'yyyy/mm/dd hh24:mi:ss') endtime from dual)
select room, sum(round((endtime - starttime)*24*60)) from
(SELECT r1.room, min(r1.starttime) starttime, nvl(r2.endtime, r1.endtime) endtime
FROM rooms r1 left join
rooms r2
ON (r1.room = r2.room
AND r2.starttime between r1.starttime and r1.endtime
AND r2.starttime <> r1.starttime)
GROUP BY r1.room, nvl(r2.endtime, r1.endtime))
GROUP BY room
output:
789 9
123 60
456 180
886 3

Related

how to add 30 minutes in last value of return query using oracle?

My query is
Select class_time from tableA
which produces:
0200AM
0230AM
0300AM
0330AM
0400AM
i want above result as
0200AM
0230AM
0300AM
0330AM
0400AM
0430AM
Assuming you are storing the times in a DATE data type (which always has year, month, day, hour, minute and second components) then add an INTERVAL literal to the maximum value from the table:
SELECT class_time from tableA
UNION ALL
SELECT MAX( class_time ) + INTERVAL '30' MINUTE FROM tableA
or add a fraction of a day:
SELECT class_time from tableA
UNION ALL
SELECT MAX( class_time ) + 30 / ( 24 /* hours */ * 60 /* minutes */ ) FROM tableA
Output:
| CLASS_TIME |
| :------------------ |
| 2019-12-16 02:00:00 |
| 2019-12-16 02:30:00 |
| 2019-12-16 03:00:00 |
| 2019-12-16 03:30:00 |
| 2019-12-16 04:00:00 |
| 2019-12-16 04:30:00 |
If your times are stored as strings then convert them to a date, add 30 minutes and then convert back to a string:
SELECT class_time from tableB
UNION ALL
SELECT TO_CHAR(
MAX( TO_DATE( class_time, 'HH12:MI AM' ) )
+ INTERVAL '30' MINUTE,
'HH12:MI AM'
)
FROM tableB
Output:
| CLASS_TIME |
| :--------- |
| 02:00 AM |
| 02:30 AM |
| 03:00 AM |
| 03:30 AM |
| 04:00 AM |
| 04:30 AM |
db<>fiddle here
You can use a CTE and union all:
with cte as (
<your query here>
)
select time
from cte
union all
select max(time) + interval '30' minute
from cte;

Partition two dates by month

I need to partition a range between two dates when the month of end date is different from the month of start date, using SQL in Oracle 11gXE.
For example:
SELECT ATIVOID, OSID, DT_INI, DT_FIM
FROM V_MTBF
WHERE ATIVOID IN ('12345TC','TCCS011701160001');
+------------------+-------------+-------------------+--------------------+
| ATIVOID | OSID | DT_INI | DT_FIM |
+------------------+-------------+-------------------+--------------------+
| 12345TC | 1710201409 | 20/10/17 14:09:58 | 01/12/17 09:03:52 |
| TCCS011701160001 | 1710201112 | 20/10/17 11:12:42 | 30/11/17 16:23:13 |
+------------------+-------------+-------------------+--------------------+
I would like to see:
+------------------+------------+-------------------+-------------------+
| ATIVOID | OSID | DT_INI | DT_FIM |
+------------------+------------+-------------------+-------------------+
| 12345TC | 1710201409 | 20/10/17 14:09:58 | 31/10/17 23:59:59 |
| 12345TC | 1710201409 | 01/11/17 00:00:00 | 30/11/17 23:59:59 |
| 12345TC | 1710201409 | 01/12/17 00:00:00 | 01/12/17 09:03:52 |
| TCCS011701160001 | 1710201112 | 20/10/17 11:12:42 | 31/10/17 23:59:59 |
| TCCS011701160001 | 1710201112 | 01/11/17 00:00:00 | 30/11/17 16:23:13 |
+------------------+------------+-------------------+-------------------+
I appreciate any help.
Try with below, Replace the static values to column and table name.
WITH TEMP_CTE(ATIVOID, OSID, DT_INI,DIF_M, LVL, DT_FIM)
AS
(SELECT '12345TC' AS , '1710201409' AS OSID, TO_DATE('20/10/17 14:09:00', 'DD/MM/YYYY HH24:MI:SS') AS DT_INI,
ABS(EXTRACT(MONTH FROM TO_DATE('20/10/17 14:09:58', 'DD/MM/YYYY HH24:MI:SS'))- EXTRACT(MONTH FROM TO_DATE('01/12/17 09:03:52', 'DD/MM/YYYY HH24:MI:SS'))) AS DIF_M,
1,
TO_DATE('01/12/17 09:03:52', 'DD-MM-YYYY HH24:MI:SS' ) AS DT_FIM
FROM DUAL
UNION ALL
SELECT ATIVOID, OSID, ADD_MONTHS(DT_INI, 1) AS DT_INI,
DIF_M, LVL + 1, DT_FIM FROM TEMP_CTE WHERE LVL <= DIF_M
)
SELECT ATIVOID, OSID,
CASE WHEN LVL = 1 THEN TO_CHAR(DT_INI, 'DD-MM-YYYY HH24:MI:SS') ELSE TO_CHAR(TRUNC(DT_INI, 'MM'), 'DD-MM-YYYY HH24:MI:SS') END AS DT_INI,
CASE WHEN LVL <= DIF_M THEN TO_CHAR(TRUNC(LAST_DAY(DT_INI) + 1) - 1/(24*60*60), 'DD-MM-YYYY HH24:MI:SS') ELSE TO_CHAR(DT_FIM, 'DD-MM-YYYY HH24:MI:SS') END AS DT_FIM FROM TEMP_CTE

How use Group by and Max(date) multi record

i want Group by by Max(Datetime) each record. but i query have dupplicatate record. i want don't duplicate record.
SQL:
select a.pmn_code,
a.ref_period,
a.SERVICE_TYPE,
min(a.status) keep (dense_rank last order by a.updated_dtm) as status,
max(a.updated_dtm) as updated_dtm
from tempChkStatus a
group by a.pmn_code, a.ref_period, a.SERVICE_TYPE
Data Table tempChkStatus:
PMN_CODE | REF_PERIOD | SERVICE_TYPE | STATUS | UPDATED_DTM
A | 01/2016 | OI | I | 19/08/2016 10:54:44
A | 01/2016 | OP | N | 06/06/2017 15:09:55
A | 02/2016 | OT | I | 31/08/2016 08:37:45
A | 02/2016 | OT | N | 12/10/2016 11:13:56
A | 04/2016 | OI | I | 19/08/2016 10:54:44
A | 04/2016 | OP | N | 06/06/2017 15:09:55
Result SQL:
PMN_CODE | REF_PERIOD | SERVICE_TYPE | STATUS | UPDATED_DTM
A | 01/2016 | OI | I | 19/08/2016 10:54:44
A | 01/2016 | OP | N | 06/06/2017 15:09:55
A | 02/2016 | OT | N | 12/10/2016 11:13:56
A | 04/2016 | OI | I | 19/08/2016 10:54:44
A | 04/2016 | OP | N | 06/06/2017 15:09:55
But I want Result:
PMN_CODE | REF_PERIOD | SERVICE_TYPE | STATUS | UPDATED_DTM
A | 01/2016 | OP | N | 06/06/2017 15:09:55
A | 02/2016 | OT | N | 12/10/2016 11:13:56
A | 04/2016 | OP | N | 06/06/2017 15:09:55
Help me please. Thanks advance ;)
with tempChkStatus (
PMN_CODE, REF_PERIOD , SERVICE_TYPE , STATUS , UPDATED_DTM) as
(
select 'A', '01/2016' ,'OI', 'I', to_date('19/08/2016 10:54:44', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 'A', '01/2016' ,'OP', 'N', to_date('06/06/2017 15:09:55', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 'A', '02/2016' ,'OT', 'I', to_date('31/08/2016 08:37:45', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 'A', '02/2016' ,'OT', 'N', to_date('12/10/2016 11:13:56', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 'A', '04/2016' ,'OI', 'I', to_date('19/08/2016 10:54:44', 'dd/mm/yyyy hh24:mi:ss') from dual union all
select 'A', '04/2016' ,'OP', 'N', to_date('06/06/2017 15:09:55', 'dd/mm/yyyy hh24:mi:ss') from dual
)
select * from (
select e.*, max(updated_dtm) over (partition by ref_period) md from tempchkstatus e
)
where updated_dtm = md
;
You just need to remove SERVICE_TYPE from the GROUP BY:
select s.pmn_code, s.ref_period,
min(s.SERVICE_TYPE) as service_type,
min(s.status) keep (dense_rank last order by s.updated_dtm) as status,
max(s.updated_dtm) as updated_dtm
from tempChkStatus s
group by s.pmn_code, s.ref_period;
The GROUP BY expressions define the rows returns by an aggregation query.
This version uses MIN() on SERVICE_TYPE. It is not clear what logic you want for the result set.

Oracle 11g - SQL to Calculate time difference between several rows

PROBLEM
I'm still finding my feet with SQL and trying to calculate how long a certain user has been scanning items during their shift.
Each scan is timestamped generating a unique 9 digit sequence number (SEQ column) and date/time in the format 05-NOV-16 15:35:24 (THE_DATE column).
The person may be scanning for several hours, and what im trying to do is subtract the first timestamp they generated from the very last timestamp at the end of their shift.
So for example given this data sample:
+-----------+--------------------+--------+---------+---------+------------+-----------+
| SEQ | THE_DATE | SCANID | LOCATN | USER_ID | FIRST_NAME | LAST_NAME |
+-----------+--------------------+--------+---------+---------+------------+-----------+
| 103939758 | 05-NOV-16 14:36:22 | 194972 | DOOR 19 | AX9868 | Mike | Derry |
| 103939780 | 05-NOV-16 14:38:07 | 194972 | DOOR 19 | AX9868 | Mike | Derry |
| 103939792 | 05-NOV-16 14:39:24 | 194972 | DOOR 19 | AX9868 | Mike | Derry |
| 103940184 | 05-NOV-16 15:16:53 | 194972 | DOOR 19 | AX9868 | Mike | Derry |
| 103940185 | 05-NOV-16 15:51:41 | 194972 | DOOR 19 | AX9868 | Mike | Derry |
| 103940214 | 05-NOV-16 09:51:42 | 194993 | DOOR 16 | BC1910 | Tony | McCann |
| 103940215 | 05-NOV-16 15:19:06 | 194993 | DOOR 16 | BC1910 | Tony | McCann |
|+-----------+--------------------+--------+---------+---------+------------------------
DESIRED RESULT
I would like to subtract the timestamp in the first row for Mike Derry, from the last row on which he appears, row 5 in this case, so that i have an answer in hours (1.25).
the final result should be grouped by day and by user_id,first_name and last_name.
So far i have looked online and at the oracle documentation ,which led me to try using the LEAD function which seemed promising. It looks at the next rows to find the next timestamp where a userid appears next and then partitions by this userid to create a new column with that timestamp.
So the SQL looked like this
SELECT SEQ, THE_DATE,SCANID,LOCATN,USER_ID,LEAD(SYSDAT ) OVER (PARTITION BY USER_ID ORDER BY SYSDAT) AS NEXT_SCAN
FROM myTable...
However this is giving me incorrect results as it seems to double count the time difference. Im sure you SQL gurus have a more elegant way around this as i dont think this function suits this particular problem :)
So the final result im trying to achieve is:
+-----------+---------+------------+-----------+-----------+
| THE_DATE | USER_ID | FIRST_NAME | LAST_NAME | TOTAL_HRS |
+-----------+---------+------------+-----------+-----------+
| 05-NOV-16 | AX9868 | Mike | Derry | 1.25 |
| 05-NOV-16 | BC1910 | Tony | McCann | 5.47 |
+-----------+---------+------------+-----------+-----------+
Your help is much appreciated
Notes.... you shouldn't have redundant data (first name, last name) in this table, you should have a separate table just for that. It seems your hours are truncated and not rounded? (the rounding would give 1.26 in the first row).
with
test_data ( seq, the_date, scanid, locatn, user_id, first_name, last_name ) as (
select 103939758, to_date('05-NOV-16 14:36:22', 'dd-MON-yy hh24:mi:ss'), 194972, 'DOOR 19', 'AX9868', 'Mike', 'Derry' from dual union all
select 103939780, to_date('05-NOV-16 14:38:07', 'dd-MON-yy hh24:mi:ss'), 194972, 'DOOR 19', 'AX9868', 'Mike', 'Derry' from dual union all
select 103939792, to_date('05-NOV-16 14:39:24', 'dd-MON-yy hh24:mi:ss'), 194972, 'DOOR 19', 'AX9868', 'Mike', 'Derry' from dual union all
select 103940184, to_date('05-NOV-16 15:16:53', 'dd-MON-yy hh24:mi:ss'), 194972, 'DOOR 19', 'AX9868', 'Mike', 'Derry' from dual union all
select 103940185, to_date('05-NOV-16 15:51:41', 'dd-MON-yy hh24:mi:ss'), 194972, 'DOOR 19', 'AX9868', 'Mike', 'Derry' from dual union all
select 103940214, to_date('05-NOV-16 09:51:42', 'dd-MON-yy hh24:mi:ss'), 194993, 'DOOR 16', 'BC1910', 'Tony', 'McCann' from dual union all
select 103940215, to_date('05-NOV-16 15:19:06', 'dd-MON-yy hh24:mi:ss'), 194993, 'DOOR 16', 'BC1910', 'Tony', 'McCann' from dual
)
-- end of test data; solution (SQL query) begins below this line
select trunc(the_date) as the_date, user_id, first_name, last_name,
trunc(24 * (max(the_date) - min(the_date)), 2) as total_hrs
from test_data
group by trunc(the_date), user_id, first_name, last_name
;
THE_DATE USER_ID FIRST_NAME LAST_NAME TOTAL_HRS
--------- ------- ---------- --------- ----------
05-NOV-16 AX9868 Mike Derry 1.25
05-NOV-16 BC1910 Tony McCann 5.45
SELECT TRUNC(THE_DATE) as THE_DATE, USER_ID, FIRST_NAME, LAST_NAME,
MAX(THE_DATE) - MIN(THE_DATE) as TOTAL_HRS
FROM yourTable
GROUP BY TRUNC(THE_DATE), USER_ID, FIRST_NAME, LAST_NAME

Oracle normalize multiple rows into new view

I need to find a solution for the following problem.
Out internal postman has to scan a QR-barcode on a mailbox first and a datamatrix-barcode on each (internal) letter he puts into the mailbox.
The data from his scanner-device is stored into a Oracle 11g database-table in the following format
|----|---------------------|--------------|---------------|
| ID | SCAN_DATE | BAROCDE_TYPE | BARCODE_VALUE |
----------------------------------------------------------|
| 1 | 2016/02/01 08:10:30 | QR | Dept_HR |
| 2 | 2016/02/01 08:10:35 | DM | Lett_1 |
| 3 | 2016/02/01 08:10:38 | DM | Lett_3 |
| 4 | 2016/02/01 08:10:41 | DM | Lett_6 |
| 5 | 2016/02/01 08:16:37 | QR | Dept_FI |
| 6 | 2016/02/01 08:16:38 | DM | Lett_2 |
| 7 | 2016/02/01 08:16:40 | DM | Lett_4 |
|----|---------------------|--------------|---------------|
I want to "normalize?" the data into a database-view in the following format
(where it easy to see which letter was delivered to which mailbox)
|---------------------|------------|---------------------|----------|
| ScanDate Postbox | Department | ScanDate Letter | LetterID |
|---------------------|------------|---------------------|----------|
| 2016/02/01 08:10:30 | Dept_HR | 2016/02/01 08:10:35 | Lett_1 |
| 2016/02/01 08:10:30 | Dept_HR | 2016/02/01 08:10:38 | Lett_3 |
| 2016/02/01 08:10:30 | Dept_HR | 2016/02/01 08:10:41 | Lett_6 |
| 2016/02/01 08:16:37 | Dept_FI | 2016/02/01 08:16:38 | Lett_2 |
| 2016/02/01 08:16:37 | Dept_FI | 2016/02/01 08:16:40 | Lett_4 |
|---------------------|------------|---------------------|----------|
Any ideas how I can create an oracle database-view showing the data as described above?
I guess the Postbox record is the previous record to the letter records. This is a bad because unsafe association.
the following select should do the job:
-- Your testdata
with data(id,
scan_date,
barcode_type,
barcode_value) as
(select 1,
to_date('2016/02/01 08:10:30', 'YYYY/MM/DD HH24:MI:SS'),
'QR',
'Dept_HR'
from dual
union all
select 2,
to_date('2016/02/01 08:10:35', 'YYYY/MM/DD HH24:MI:SS'),
'DM',
'Lett_1'
from dual
union all
select 3,
to_date('2016/02/01 08:10:38', 'YYYY/MM/DD HH24:MI:SS'),
'DM',
'Lett_3'
from dual
union all
select 4,
to_date('2016/02/01 08:10:41', 'YYYY/MM/DD HH24:MI:SS'),
'DM',
'Lett_6'
from dual
union all
select 5,
to_date('2016/02/01 08:16:37', 'YYYY/MM/DD HH24:MI:SS'),
'QR',
'Dept_FI'
from dual
union all
select 6,
to_date('2016/02/01 08:16:38', 'YYYY/MM/DD HH24:MI:SS'),
'DM',
'Lett_2'
from dual
union all
select 7,
to_date('2016/02/01 08:16:40', 'YYYY/MM/DD HH24:MI:SS'),
'DM',
'Lett_4'
from dual)
-- Select
select dp.scan_date as "ScanDate Postbox",
dp.barcode_value as "Departement",
d.scan_date as "ScanDate Letter",
d.barcode_value as "LetterId"
from data dp, data d
where d.barcode_type = 'DM'
and dp.barcode_type = 'QR'
and dp.scan_date =
(select max(dpp.scan_date)
from data dpp
where dpp.barcode_type = dp.barcode_type
and dpp.scan_date <= d.scan_date);