简体   繁体   中英

print start and end date in one row for continous or overlaping date ranges in oracle SQL

I would like to print in one row start date and end date for continous or overlaping date ranges.

here is the data

create table orders (
po varchar2(6),
startdate date,
enddate date
);

insert into orders values ('order1',to_date('01-01-2020','dd-MM-yyyy'),to_date('31-01-2020','dd-MM-yyyy'));
insert into orders values ('order1',to_date('01-02-2020','dd-MM-yyyy'),to_date('31-03-2020','dd-MM-yyyy'));
insert into orders values ('order1',to_date('01-04-2020','dd-MM-yyyy'),to_date('30-06-2020','dd-MM-yyyy'));
insert into orders values ('order2',to_date('01-01-2020','dd-MM-yyyy'),to_date('31-01-2020','dd-MM-yyyy'));
insert into orders values ('order2',to_date('01-03-2020','dd-MM-yyyy'),to_date('31-03-2020','dd-MM-yyyy'));
insert into orders values ('order3',to_date('01-01-2020','dd-MM-yyyy'),to_date('31-01-2020','dd-MM-yyyy'));
insert into orders values ('order3',to_date('02-02-2020','dd-MM-yyyy'),to_date('31-05-2020','dd-MM-yyyy'));
insert into orders values ('order3',to_date('01-05-2020','dd-MM-yyyy'),to_date('31-07-2020','dd-MM-yyyy'));

expected output is

order1  01-01-2020   30-06-2020
order2  01-01-2020   31-01-2020
order2  01-03-2020   31-03-2020
order3  01-01-2020   31-01-2020
order3  02-02-2020   31-07-2020

first I tried to use unpivot clause to get all dates in one column and to check previous and following rows if they are overlaping or continous and then eliminate this rows but it won't work because if there is overlap the order of dates will be not startdate following by enddate anymore.

this won't work as a starting point

select * from(
select * from (
select po,startdate,enddate from orders)
unpivot(column_val for column_name in (startdate,enddate)) )order by po,column_val

any other solutions for that?

There is an elegant (and efficient) solution using the match_recognize clause (which requires Oracle 12.1 or higher).

select po, startdate, enddate
from   orders
match_recognize (
  partition by po
  order     by startdate
  measures  first(startdate) as startdate, max(enddate) as enddate
  pattern   ( c* n )
  define    c as max(enddate) + 1 >= next(startdate)  
);

Use window functions to see if there is any overlap with previous records. Then a cumulative sum to assign a "grouping" and aggregate.

I like to use a cumulative max for more generic overlaps:

select po, min(startdate), max(enddate)
from (select o.*,
             sum(case when prev_enddate >= startdate then 0 else 1 end) over (partition by po order by startdate) as grouping
      from (select o.*,
                   max(enddate) over (partition by po order by startdate range between unbounded preceding and '1' second preceding) as prev_enddate
            from orders o
           ) o
     ) o
group by po, grouping;

In many cases, you can use lag() instead of max() :

select po, min(startdate), max(enddate)
from (select o.*,
             sum(case when prev_enddate >= startdate then 0 else 1 end) over (partition by po order by startdate) as grouping
      from (select o.*,
                   lag(enddate) over (partition by po order by startdate) as prev_enddate
            from orders o
           ) o
     ) o
group by po, grouping;

This works so long as the previous row has the overlap, which is typically the case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM