Partition two dates by month - sql

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

Related

oracle sql query time periods union of set

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

Hierarchical query for mulitiple groups

I'd like to generate every month end date between two dates but I need to do that for multiple groups. Simplified example is here:
select last_day(add_months(date '2015-01-01', level - 1)), 1 gr from dual
connect by level <= 12
union all
select last_day(add_months(date '2015-01-01', level - 1)), 2 gr from dual
connect by level <= 12;
Can it be done in single SQL query without union all as I have many groups.
I know I can do it with PL/SQL but just out of curiosity it is possible to do with single SQL statement?
I'd like query like this one:
with d as (
select date '2015-01-01' start_date, date '2015-12-01' end_date, 1 gr from dual
union all
select date '2015-01-01' start_date, date '2015-12-01' end_date, 2 gr from dual
)
select last_day(add_months(start_date, level - 1)) from d
start with start_date = date '2015-01-01'
connect by level <= months_between(end_date, start_date);
but generating results as first query does not cross join
You can use the PRIOR and SYS_GUID() option
WITH d
AS (SELECT DATE '2015-01-01' start_date,
DATE '2015-12-01' end_date,
1 gr
FROM dual
UNION ALL
SELECT DATE '2015-01-01' start_date,
DATE '2015-12-01' end_date,
2 gr
FROM dual)
SELECT gr,
last_day(add_months(start_date, LEVEL - 1)) AS dt
FROM d
START WITH start_date = DATE '2015-01-01'
CONNECT BY LEVEL <= months_between(end_date, start_date)
AND PRIOR gr = gr
AND PRIOR sys_guid() IS NOT NULL
ORDER BY gr,
dt
| GR | DT |
|----|----------------------|
| 1 | 2015-01-31T00:00:00Z |
| 1 | 2015-02-28T00:00:00Z |
| 1 | 2015-03-31T00:00:00Z |
| 1 | 2015-04-30T00:00:00Z |
| 1 | 2015-05-31T00:00:00Z |
| 1 | 2015-06-30T00:00:00Z |
| 1 | 2015-07-31T00:00:00Z |
| 1 | 2015-08-31T00:00:00Z |
| 1 | 2015-09-30T00:00:00Z |
| 1 | 2015-10-31T00:00:00Z |
| 1 | 2015-11-30T00:00:00Z |
| 2 | 2015-01-31T00:00:00Z |
| 2 | 2015-02-28T00:00:00Z |
| 2 | 2015-03-31T00:00:00Z |
| 2 | 2015-04-30T00:00:00Z |
| 2 | 2015-05-31T00:00:00Z |
| 2 | 2015-06-30T00:00:00Z |
| 2 | 2015-07-31T00:00:00Z |
| 2 | 2015-08-31T00:00:00Z |
| 2 | 2015-09-30T00:00:00Z |
| 2 | 2015-10-31T00:00:00Z |
| 2 | 2015-11-30T00:00:00Z |
Demo
I've found one more way but lateral is also not what I was aiming for. Kaushik's answer is what I was looking for.
with data as (
select date '2015-01-01' start_date, date '2015-12-01' end_date, 1 as gr from dual
union all
select date '2015-01-01' start_date, date '2015-12-01' end_date, 2 as gr from dual
),
data_level as(
select start_Date
,end_date
,gr
,months_between(end_date,start_date) + 1 as lvl
from data)
select g from data_level,
lateral(select last_day(add_months(start_date,level - 1)) g
from dual
connect by level <= lvl
);

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 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);

SQL/Postgres datetime division / normalizing

I have this activity table
+--------------+------------------+
| Field | Type |
+--------------+------------------+
| id | int(11) unsigned |
| start_date | timestamp |
| end_date | timestamp |
| ... | |
+--------------+------------------+
I need a view which groups these activities by start_date by DAY, but in such a way that, if the end_date is not in the same day as start_date, the view contain the entry again but with the start_date set to 00:00 of the next day.. (and so on, repeated as many times as needed until the start_date is in the same day as the end_date)
As an example:
if the activity table contains:
+--------------+----------------------------+----------------------------+
| id | start_date | end_date |
+--------------+----------------------------+----------------------------+
| 1 | 2014-12-02 14:12:00+00 | 2014-12-03 06:45:00+00 |
| 2 | 2014-12-05 15:25:00+00 | 2014-12-05 07:29:00+00 |
+--------------+----------------------------+----------------------------+
The view should contain:
+--------------+----------------------------+----------------------------+
| activity_id | start_date | end_date |
+--------------+----------------------------+----------------------------+
| 1 | 2014-12-02 14:12:00+00 | 2014-12-02 23:59:59+00 |
| 1 | 2014-12-03 00:00:00+00 | 2014-12-03 06:45:00+00 |
| 2 | 2014-12-05 15:25:00+00 | 2014-12-05 07:29:00+00 |
+--------------+----------------------------+----------------------------+
Any help would be greatly appreciated!
PS: I'm using postgresql
To get the needed rows, start by using a set returning function along with a lateral join. From there, use CASE statements and date arithmetics to pull out the relevant values.
Here's an example to get you started:
with data as (
select id, start_date, end_date
from (values
(1, '2014-12-02 14:12:00+00'::timestamptz, '2014-12-03 06:45:00+00'::timestamptz),
(2, '2014-12-05 15:25:00+00'::timestamptz, '2014-12-05 07:29:00+00'::timestamptz)
) as rows (id, start_date, end_date)
)
select data.id,
case days.d = date_trunc('day', data.start_date)
when true then data.start_date
else days.d
end as start_date,
case days.d = date_trunc('day', data.end_date)
when true then data.end_date
else days.d + interval '1 day' - interval '1 sec'
end as end_date
from data
join generate_series(
date_trunc('day', data.start_date),
date_trunc('day', data.end_date),
'1 day'
) as days (d)
on days.d >= date_trunc('day', data.start_date)
and days.d <= date_trunc('day', data.end_date)
id | start_date | end_date
----+------------------------+------------------------
1 | 2014-12-02 15:12:00+01 | 2014-12-02 23:59:59+01
1 | 2014-12-03 00:00:00+01 | 2014-12-03 07:45:00+01
2 | 2014-12-05 16:25:00+01 | 2014-12-05 08:29:00+01
(3 rows)
As an aside, depending on what you're doing, it might make more sense for you to use a date range:
with data as (
select id, start_date, end_date
from (values
(1, '2014-12-02 14:12:00+00'::timestamptz, '2014-12-03 06:45:00+00'::timestamptz),
(2, '2014-12-05 07:25:00+00'::timestamptz, '2014-12-05 15:29:00+00'::timestamptz)
) as rows (id, start_date, end_date)
)
select data.id,
tstzrange(data.start_date, data.end_date)
from data;
id | tstzrange
----+-----------------------------------------------------
1 | ["2014-12-02 15:12:00+01","2014-12-03 07:45:00+01")
2 | ["2014-12-05 08:25:00+01","2014-12-05 16:29:00+01")
(2 rows)