How to fill date range gaps Oracle SQL - sql

With a given dataset:
WITH ranges AS (
select to_date('01.01.2021 00:00:00','DD.MM.YYYY hh24:mi:ss') date_from,
to_date('31.03.2021 00:00:00','DD.MM.YYYY hh24:mi:ss') date_to
from dual
union
select to_date('27.03.2021 00:00:00','DD.MM.YYYY hh24:mi:ss') date_from,
to_date('27.04.2021 00:00:00','DD.MM.YYYY hh24:mi:ss') date_to
from dual
union
select to_date('01.05.2021 00:00:00','DD.MM.YYYY hh24:mi:ss') date_from,
to_date('31.12.2021 00:00:00','DD.MM.YYYY hh24:mi:ss') date_to
from dual
)
SELECT * FROM ranges;
How to find the gap 28.04.2021-30.04.2021.? Also consider that there can be multiple gaps in between and ranges can overlap.
Any suggestion?

Try this query, tune to your needs:
WITH steps AS (
SELECT date_from as dt, 1 as step FROM ranges
UNION ALL
SELECT date_to as dt, -1 as step FROM ranges
)
SELECT dt as dt_from,
lead(dt) over (order by dt) as dt_to,
sum(step) over (order by dt) as cnt_ranges
FROM steps;
dt_from | dt_to | cnt_ranges
------------------------+-------------------------+-----------
2021-01-01 00:00:00.000 | 2021-03-27 00:00:00.000 | 1
2021-03-27 00:00:00.000 | 2021-03-31 00:00:00.000 | 2
2021-03-31 00:00:00.000 | 2021-04-27 00:00:00.000 | 1
2021-04-27 00:00:00.000 | 2021-05-01 00:00:00.000 | 0
2021-05-01 00:00:00.000 | 2021-12-31 00:00:00.000 | 1
2021-12-31 00:00:00.000 | | 0

You are modeling date ranges incorrectly; an interval ending at midnight on 02-14-2021, for example, should not include 02-14-2021. In your model it does.
This leads to unnecessary complications in all the queries you write against your model. In the solution below I need to add 1 to end dates first, do all the processing, and then subtract 1 at the end.
with
ranges (date_from, date_to) as (
select to_date('01.01.2021 00:00:00','DD.MM.YYYY hh24:mi:ss'),
to_date('31.03.2021 00:00:00','DD.MM.YYYY hh24:mi:ss')
from dual
union all
select to_date('27.03.2021 00:00:00','DD.MM.YYYY hh24:mi:ss'),
to_date('27.04.2021 00:00:00','DD.MM.YYYY hh24:mi:ss')
from dual
union all
select to_date('01.05.2021 00:00:00','DD.MM.YYYY hh24:mi:ss'),
to_date('31.12.2021 00:00:00','DD.MM.YYYY hh24:mi:ss')
from dual
)
select first_missing, last_missing - 1 as last_missing
from (
select dt as first_missing,
lead(df) over (order by dt) as last_missing
from (select date_from, date_to + 1 as date_to from ranges)
match_recognize(
order by date_from
measures first(date_from) as df, max(date_to) as dt
pattern (a* b)
define a as max(date_to) >= next (date_from)
)
)
where last_missing is not null
;
FIRST_MISSING LAST_MISSING
------------------- -------------------
28.04.2021 00:00:00 30.04.2021 00:00:00

Related

Find peaks of data

So I have a table Integrations.
Inte
Start Date
End Date
Total_Duration
INT1
1/7/2021 7:16:00
1/7/2021 9:22:00
02:06:00
INt2
2/7/2021 3:48:00
2/7/2021 5:10:00
01:22:00
Output I need:
Running Time
No of Inte.
1/7/2021 7:00:00
1
1/7/2021 8:00:00
1
1/7/2021 9:00:00
1
2/7/2021 4:00:00
1
2/7/2021 5:00:00
1
Basically it want to plot the peak hour when most Integrations were running.
Sql query I wrote:
select time, sum(value) as No_of_Inte
from(
select round(Start_Date, 'HH24') as time, count(*) as value
from Integrations
group by Start_Date
)
group by time
order by time asc
But this does not consider Total Duration.
Output :
Running Time
No of Inte.
1/7/2021 7:00:00
1
2/7/2021 4:00:00
1
Also, new Integrations are added every day.
This can be done using a recursive query. First create the test data
CREATE TABLE integrations (inte,start_date, end_date)
AS
(
SELECT 'INT1', TO_DATE('1/7/2021 7:16:00','DD/MM/YYYY HH24:MI:SS'), TO_DATE('1/7/2021 9:22:00','DD/MM/YYYY HH24:MI:SS') FROM dual UNION ALL
SELECT 'INT2', TO_DATE('2/7/2021 3:48:00','DD/MM/YYYY HH24:MI:SS'), TO_DATE('2/7/2021 5:10:00','DD/MM/YYYY HH24:MI:SS') FROM dual
);
Now use a recursive query to loop through the hours between start and end date. Then group by hour to get the correct counts per hour.
WITH row_per_hours (id, run_hour, end_date) AS
(
SELECT inte,
TRUNC(start_date,'HH24'),
end_date
FROM integrations
UNION ALL
SELECT id,
run_hour + INTERVAL '1' HOUR,
end_date
FROM row_per_hours
WHERE run_hour + INTERVAL '1' HOUR < end_date
)
SELECT TO_CHAR(run_hour,'DD/MM/YYYY HH24:MI:SS') as running_time,
COUNT(id) as integration_count
FROM row_per_hours
GROUP BY TO_CHAR(run_hour,'DD/MM/YYYY HH24:MI:SS') ORDER BY 1;
RUNNING_TIME INTEGRATION_COUNT
------------------- -----------------
01/07/2021 07:00:00 1
01/07/2021 08:00:00 1
01/07/2021 09:00:00 1
02/07/2021 03:00:00 1
02/07/2021 04:00:00 1
02/07/2021 05:00:00 1
For 12C and above:
You may use lateral join to generate required number of rows per each interval. Since it looks like you need some rounding of dates towards neares hour, I've added round instead of trunc. Or is there any other reason for the first interval is treating 7:00 as inclusion?.
with a(Inte, start_dt, end_dt) as (
select
'INT1'
, to_date('1/7/2021 07:16:00', 'dd/mm/yyyy hh24:mi:ss')
, to_date('1/7/2021 09:22:00', 'dd/mm/yyyy hh24:mi:ss')
from dual union all
select
'INt2'
, to_date('2/7/2021 03:48:00', 'dd/mm/yyyy hh24:mi:ss')
, to_date('2/7/2021 05:10:00', 'dd/mm/yyyy hh24:mi:ss')
from dual
)
select /*+ gather_plan_statistics */
b.hour_
, count(1) as int_cnt
from a
outer apply (
select
round(a.start_dt + numtodsinterval(level - 1, 'HOUR'), 'hh24') as hour_
from dual
connect by round(start_dt, 'hh24') + numtodsinterval(level - 1, 'HOUR') <= trunc(end_dt, 'hh24')
) b
group by b.hour_
order by 1
HOUR_ | INT_CNT
:------------------ | ------:
2021-07-01 07:00:00 | 1
2021-07-01 08:00:00 | 1
2021-07-01 09:00:00 | 1
2021-07-02 04:00:00 | 1
2021-07-02 05:00:00 | 1
db<>fiddle here

Split records based on date in sql

ID EFF_DT END_DT
FLA1 2018-01-01 00:00:00 2019-12-31 00:00:00
FLA1 2020-01-01 00:00:00 9999-12-31 00:00:00
The above structure needs to be splited. And the split should be based on the date.
the output should have additional column as year
ID EFF_DT END_DT YEAR
FLA1 2018-01-01 00:00:00 2019-12-31 00:00:00 2019
FLA1 2020-01-01 00:00:00 2020-12-31 00:00:00 2020
FLA1 2021-01-01 00:00:00 9999-12-31 00:00:00 2021
I am using union for this purpose and it is generating duplicates. Any other approach / refine solution will work. Thanks in advance.
You can use a recursive sub-query factoring clause:
WITH split ( ID, EFF_DT, END_DT, MAX_DT ) AS (
SELECT id,
eff_dt,
LEAST(
ADD_MONTHS( TRUNC( SYSDATE, 'YY' ), 12 ) - INTERVAL '1' DAY,
end_dt
),
end_dt
FROM table_name
UNION ALL
SELECT id,
end_dt + INTERVAL '1' DAY,
max_dt,
max_dt
FROM split
WHERE end_dt < max_dt
)
SELECT id,
eff_dt,
end_dt
FROM split;
Which, for your sample data:
CREATE TABLE table_name ( ID, EFF_DT, END_DT ) AS
SELECT 'FLA1', DATE '2018-01-01', DATE '2019-12-31' FROM DUAL UNION ALL
SELECT 'FLA1', DATE '2020-01-01', DATE '9999-12-31' FROM DUAL;
Outputs:
ID | EFF_DT | END_DT
:--- | :------------------ | :------------------
FLA1 | 2018-01-01 00:00:00 | 2019-12-31 00:00:00
FLA1 | 2020-01-01 00:00:00 | 2020-12-31 00:00:00
FLA1 | 2021-01-01 00:00:00 | 9999-12-31 00:00:00
db<>fiddle here
If you want to generate all years of data, then:
with cte (id, eff_dt, end_dt, orig_end_dt)
select id, eff_dt, end_dt, end_dt
from t
union all
select cte.id, end_dt + interval '1' day,
least(orig_end_dte, trunc(end_dt, 'YYYY') + interval '1' year
from cte
where trunc(eff_dt, 'YYYY') < trunc(end_dt, 'YYYY')
)
select id, eff_dt, end_dt, to_char(end_dt, 'YYYY') as year
from cte;
Note: This produces a separate row for every year in the period.
If you want a limit on the year, then it would be something like this:
with cte (id, eff_dt, end_dt, orig_end_dt)
select id, eff_dt, end_dt, end_dt
from t
union all
select cte.id, end_dt + interval '1' day,
least(orig_end_dte, trunc(end_dt, 'YYYY') + interval '1' year
from cte
where trunc(eff_dt, 'YYYY') < least(trunc(end_dt, 'YYYY'), date '2021-01-01')
)
select id, eff_dt,
(case when end_dt = date '2021-12-31' then orig_end_dt else end_dt end),
to_char(end_dt, 'YYYY') as year
from cte;

how to get different date ranges for the last One year as per sysdate

I have a scenario: as per my sysdate i need to capture last 12 months dates.
Example: I will be getting a parameter which is numeric like : 2,3,4,6 ..
If the parameter is 3: then as per sysdate-12 i am expecting 4 records as below
Start_Date End_Date
20180801 20181101
20181101 20190201
20190201 20190501
20190501 20190827
select TO_CHAR(add_months(trunc(sysdate, 'month'), -12),'YYYYMMDD') Start_Date,TO_CHAR(add_months(trunc(sysdate, 'month'), -9),'YYYYMMDD') End_Date from dual
union all
select TO_CHAR(add_months(trunc(sysdate, 'month'), -9),'YYYYMMDD') Start_Date,TO_CHAR(add_months(trunc(sysdate, 'month'), -6),'YYYYMMDD') End_Date from dual
union all
select TO_CHAR(add_months(trunc(sysdate, 'month'), -6),'YYYYMMDD') Start_Date,TO_CHAR(add_months(trunc(sysdate, 'month'), -3),'YYYYMMDD') End_Date from dual
union all
select TO_CHAR(add_months(trunc(sysdate, 'month'), -3),'YYYYMMDD') Start_Date,TO_CHAR(trunc(sysdate),'YYYYMMDD') End_Date from dual
Between two dates i have difference of 3 months. If the parameter is 2 then difference between Start_Date and End_Date should be 2 months which means i will be getting 6 records.
Can we write a query to read this numeric parameter and create records on the base of the parameter. Without writing multiple queries like above , is there any possibility i can read the parameter and create records
You can use the following query:
SELECT
ADD_MONTHS(START_,(LEVEL - 1) * &&INPUT_NUMBER) AS START_DATE,
CASE
WHEN LEVEL = 12 / ( &&INPUT_NUMBER ) THEN SYSDATE
ELSE ADD_MONTHS(START_,(LEVEL) * &&INPUT_NUMBER)
END AS END_DATE
FROM
(
SELECT
ADD_MONTHS(TRUNC(SYSDATE, 'MONTH'), - 12) START_
FROM
DUAL
)
CONNECT BY
LEVEL <= 12 / ( &&INPUT_NUMBER );
-- With Input parameter as 2
SQL> SELECT
2 ADD_MONTHS(START_,(LEVEL - 1) * &&INPUT_NUMBER) AS START_DATE,
3 CASE
4 WHEN LEVEL = 12 / ( &&INPUT_NUMBER ) THEN SYSDATE
5 ELSE ADD_MONTHS(START_,(LEVEL) * &&INPUT_NUMBER)
6 END AS END_DATE
7 FROM
8 (
9 SELECT
10 ADD_MONTHS(TRUNC(SYSDATE, 'MONTH'), - 12) START_
11 FROM
12 DUAL
13 )
14 CONNECT BY
15 LEVEL <= 12 / ( &&INPUT_NUMBER )
16 ;
START_DAT END_DATE
--------- ---------
01-AUG-18 01-OCT-18
01-OCT-18 01-DEC-18
01-DEC-18 01-FEB-19
01-FEB-19 01-APR-19
01-APR-19 01-JUN-19
01-JUN-19 27-AUG-19
6 rows selected.
SQL>
-- With Input parameter as 3
SQL> SELECT
2 ADD_MONTHS(START_,(LEVEL - 1) * &&INPUT_NUMBER) AS START_DATE,
3 CASE
4 WHEN LEVEL = 12 / ( &&INPUT_NUMBER ) THEN SYSDATE
5 ELSE ADD_MONTHS(START_,(LEVEL) * &&INPUT_NUMBER)
6 END AS END_DATE
7 FROM
8 (
9 SELECT
10 ADD_MONTHS(TRUNC(SYSDATE, 'MONTH'), - 12) START_
11 FROM
12 DUAL
13 )
14 CONNECT BY
15 LEVEL <= 12 / ( &&INPUT_NUMBER )
16 ;
Enter value for input_number: 3
START_DAT END_DATE
--------- ---------
01-AUG-18 01-NOV-18
01-NOV-18 01-FEB-19
01-FEB-19 01-MAY-19
01-MAY-19 27-AUG-19
SQL>
Cheers!!
Basically the same idea as #Tejash, using a hierarchical query but with a bind variable instead of a substitution variable, and counting backwards instead of forwards:
var your_var number;
exec :your_var := 3;
select
add_months(trunc(sysdate, 'MM'), -(level) * :your_var) as period_start,
case when level = 1 then trunc(sysdate, 'DD' )
else add_months(trunc(sysdate, 'MM'), -(level - 1) * :your_var)
end as period_end
from dual
connect by level <= 12/:your_var
order by period_start;
PERIOD_STA PERIOD_END
---------- ----------
2018-08-01 2018-11-01
2018-11-01 2019-02-01
2019-02-01 2019-05-01
2019-05-01 2019-08-27
exec :your_var := 2;
...
PERIOD_STA PERIOD_END
---------- ----------
2018-08-01 2018-10-01
2018-10-01 2018-12-01
2018-12-01 2019-02-01
2019-02-01 2019-04-01
2019-04-01 2019-06-01
2019-06-01 2019-08-27
But you could also do this with recursive subquery factoring:
exec :your_var := 3;
with rcte (period_start, period_end, final_end) as (
select add_months(trunc(sysdate, 'MM'), -:your_var),
trunc(sysdate, 'DD'),
add_months(trunc(sysdate, 'MM'), -12)
from dual
union all
select add_months(period_start, -:your_var),
add_months(trunc(period_end, 'MM'), -:your_var),
final_end
from rcte
where period_start > final_end
)
select period_start, period_end
from rcte
order by period_start;
PERIOD_STA PERIOD_END
---------- ----------
2018-08-01 2018-11-01
2018-11-01 2019-02-01
2019-02-01 2019-05-01
2019-05-01 2019-08-27
exec :your_var := 2;
...
PERIOD_STA PERIOD_END
---------- ----------
2018-08-01 2018-10-01
2018-10-01 2018-12-01
2018-12-01 2019-02-01
2019-02-01 2019-04-01
2019-04-01 2019-06-01
2019-06-01 2019-08-27
Or with intervals:
with rcte (period_start, period_end, final_end) as (
select trunc(sysdate, 'MM') -:your_var * interval '1' month,
trunc(sysdate, 'DD'),
trunc(sysdate, 'MM') - interval '1' year
from dual
union all
select period_start -:your_var * interval '1' month,
trunc(period_end, 'MM') -:your_var * interval '1' month,
final_end
from rcte
where period_start > final_end
)
select period_start, period_end
from rcte
order by period_start;

SQL , Analytical Functions , rownumber

I need to get same rownumber or numeric value in SQL to group values that match conditions like the following example:
If we have same Agent name and the time variance between current row and preceding row value is less than 06:00 hours after applying partition by name and ordering by time
then add same rownumber else increase it.
example for row data and output of rownumber:
person date_time rownumber
A 01/04/2018 10:00 1
A 01/04/2018 13:00 1
A 01/04/2018 14:00 1
A 01/04/2018 15:00 1
A 01/04/2018 23:00 2
A 02/04/2018 03:00 2
A 02/04/2018 12:00 3
A 02/04/2018 16:00 3
B 01/04/2018 17:00 4
B 01/04/2018 20:30 4
C 01/04/2018 18:00 5
C 01/04/2018 22:00 5
You can do this with a combination of LAG and SUM analytic functions, like so:
WITH your_table AS (SELECT 'A' person, to_date('01/04/2018 10', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'A' person, to_date('01/04/2018 13', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'A' person, to_date('01/04/2018 14', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'A' person, to_date('01/04/2018 15', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'A' person, to_date('01/04/2018 23', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'A' person, to_date('02/04/2018 03', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'A' person, to_date('02/04/2018 12', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'A' person, to_date('02/04/2018 16', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'B' person, to_date('01/04/2018 17', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'B' person, to_date('01/04/2018 20', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'C' person, to_date('01/04/2018 18', 'dd/mm/yyyy hh24') date_time FROM dual UNION ALL
SELECT 'C' person, to_date('01/04/2018 22', 'dd/mm/yyyy hh24') date_time FROM dual)
SELECT person,
date_time,
SUM(period_change) OVER (ORDER BY person, date_time) rownumber
FROM (SELECT person,
date_time,
CASE WHEN date_time - LAG(date_time, 1, date_time - 7/24) OVER (PARTITION BY person ORDER BY date_time) > 6/24 THEN 1 ELSE 0 END period_change
FROM your_table);
PERSON DATE_TIME ROWNUMBER
------ ----------- ----------
A 01/04/2018 1
A 01/04/2018 1
A 01/04/2018 1
A 01/04/2018 1
A 01/04/2018 2
A 02/04/2018 2
A 02/04/2018 3
A 02/04/2018 3
B 01/04/2018 4
B 01/04/2018 4
C 01/04/2018 5
C 01/04/2018 5
This works by putting 1 in the additional column whenever a new group is triggered.
Once you have that, then you can do a running sum on that column. That means that after every group change, subsequent rows will be assigned the same number, up until the next group change.
N.B. As suggested by Peter Lang in the comments below, you might prefer to change the case statement generating the "period_change" column to:
CASE WHEN date_time - LAG(date_time) OVER (PARTITION BY person ORDER BY date_time) < 6/24 THEN 0 ELSE 1 END

Oracle Dynamic Range

So, i have this sample data:
Department | InitialDate | FinalDate
-------------------------------------------------------
1 | 01/01/2017 01:12:00 | 01/03/2017 00:00:08
1 | 01/03/2017 00:00:08 | 01/04/2017 05:00:01
1 | 01/04/2017 05:00:01 | 01/05/2017 02:00:00
2 | 01/05/2017 10:00:00 | 01/06/2017 11:00:08
2 | 01/06/2017 11:00:08 | 01/07/2017 04:04:00
3 | 01/07/2017 04:00:00 | 01/07/2017 15:00:22
1 | 01/07/2017 14:00:00 | 01/07/2017 18:00:08
1 | 01/07/2017 18:15:00 | 01/08/2017 22:00:00
3 | 01/12/2017 01:30:03 | 01/12/2017 18:00:00
1 | 01/13/2017 23:12:00 | 01/13/2017 23:59:08
and want to group it like this
Department | InitialDate | FinalDate
-------------------------------------------------------
1 | 01/01/2017 01:12:00 | 01/05/2017 02:00:00
2 | 01/05/2017 10:00:00 | 01/07/2017 04:04:00
3 | 01/07/2017 04:00:00 | 01/07/2017 15:00:22
1 | 01/07/2017 14:00:00 | 01/08/2017 22:00:00
3 | 01/12/2017 01:30:03 | 01/12/2017 18:00:00
1 | 01/13/2017 23:12:00 | 01/13/2017 23:59:08
I need to make groups by department and get the first and last date of each group, but the departments can repeat and for each time it occurs, I want the first and last date of that specific window. I already tried Analytic functions but nothing seems to work.
You can do it using the LAG analytic function to compare each row with the previous row:
SELECT department,
MIN( InitialDate ) AS InitialDate,
MIN( FinalDate ) AS FinalDate
FROM (
SELECT department,
InitialDate,
FinalDate,
SUM( grp_inc ) OVER ( ORDER BY FinalDate ) AS grp
FROM (
SELECT department,
InitialDate,
FinalDate,
CASE WHEN LAG( department ) OVER ( ORDER BY FinalDate ) = department
THEN 0
ELSE 1
END AS grp_inc
FROM table_name
)
)
GROUP BY department, grp
This is a type of "gaps-and-islands" problem. One method of solving it is by determining where groups of overlapping times start. Then use a cumulative sum to define each group:
select departmentid, min(initialdate), max(finaldate)
from (select t.*, sum(grp_starts) over (partition by departmentid order by initialdate) as grp
from (select t.*,
(case when exists (select 1
from t t2
where t2.departmentid = t.departmentid and
t.initialdate > t2.initialdate and
t.initialdate <= t2.finaldate
)
then 0 else 1
end) as grp_starts
from t
) t
) t
group by departmentid, grp;
Since you are looking for where the department changes and not where the department changes or the initialdate is not the same as the previous row's finaldate, you can use tabibitosan
WITH sample_data AS (SELECT 1 department, to_date('01/01/2017 01:12:00', 'mm/dd/yyyy hh24:mi:ss') initialdate, to_date('01/03/2017 00:00:08', 'mm/dd/yyyy hh24:mi:ss') finaldate from dual union all
SELECT 1 department, to_date('01/03/2017 00:00:08', 'mm/dd/yyyy hh24:mi:ss') initialdate, to_date('01/04/2017 05:00:01', 'mm/dd/yyyy hh24:mi:ss') finaldate from dual union all
SELECT 1 department, to_date('01/04/2017 05:00:01', 'mm/dd/yyyy hh24:mi:ss') initialdate, to_date('01/05/2017 02:00:00', 'mm/dd/yyyy hh24:mi:ss') finaldate from dual union all
SELECT 2 department, to_date('01/05/2017 10:00:00', 'mm/dd/yyyy hh24:mi:ss') initialdate, to_date('01/06/2017 11:00:08', 'mm/dd/yyyy hh24:mi:ss') finaldate from dual union all
SELECT 2 department, to_date('01/06/2017 11:00:08', 'mm/dd/yyyy hh24:mi:ss') initialdate, to_date('01/07/2017 04:04:00', 'mm/dd/yyyy hh24:mi:ss') finaldate from dual union all
SELECT 3 department, to_date('01/07/2017 04:00:00', 'mm/dd/yyyy hh24:mi:ss') initialdate, to_date('01/07/2017 15:00:22', 'mm/dd/yyyy hh24:mi:ss') finaldate from dual union all
SELECT 1 department, to_date('01/07/2017 14:00:00', 'mm/dd/yyyy hh24:mi:ss') initialdate, to_date('01/07/2017 18:00:08', 'mm/dd/yyyy hh24:mi:ss') finaldate from dual union all
SELECT 1 department, to_date('01/07/2017 18:15:00', 'mm/dd/yyyy hh24:mi:ss') initialdate, to_date('01/08/2017 22:00:00', 'mm/dd/yyyy hh24:mi:ss') finaldate from dual union all
SELECT 3 department, to_date('01/12/2017 01:30:03', 'mm/dd/yyyy hh24:mi:ss') initialdate, to_date('01/12/2017 18:00:00', 'mm/dd/yyyy hh24:mi:ss') finaldate from dual union all
SELECT 1 department, to_date('01/13/2017 23:12:00', 'mm/dd/yyyy hh24:mi:ss') initialdate, to_date('01/13/2017 23:59:08', 'mm/dd/yyyy hh24:mi:ss') finaldate from dual)
SELECT department,
MIN(initialdate) initialdate,
MAX(finaldate) finaldate
FROM (SELECT department,
initialdate,
finaldate,
row_number() OVER (ORDER BY initialdate)
- row_number() OVER (PARTITION BY department ORDER BY initialdate) grp
FROM sample_data sd)
GROUP BY department, grp
ORDER BY initialdate;
DEPARTMENT INITIALDATE FINALDATE
---------- ------------------- -------------------
1 01/01/2017 01:12:00 01/05/2017 02:00:00
2 01/05/2017 10:00:00 01/07/2017 04:04:00
3 01/07/2017 04:00:00 01/07/2017 15:00:22
1 01/07/2017 14:00:00 01/08/2017 22:00:00
3 01/12/2017 01:30:03 01/12/2017 18:00:00
1 01/13/2017 23:12:00 01/13/2017 23:59:08
This works by walking through and numbering all rows ordered by initial date and comparing them to walking through and numbering the rows for each department. When the department changes, the difference between the numbers changes. Where a department has consecutive rows in the initial dataset, the difference will remain the same for those rows. E.g. in your data, department 1 has 6 rows, the first 3 rows are the same as the first 3 rows of the initial data set, so the difference for those three rows is 0. The fourth and fifth department 1 rows are the 7th and 8th rows in the dataset, so the difference is 3 for those rows, etc.
This gives us a number that we can use, in conjunction with the department number, to group the data by. It's then a simple matter of finding the min/max dates within that group.