Merging adjacent date intervals into a single row

gaps-and-islandsoracle

Here is the scenario:
If the employee transaction is continuous display as-is. if the employee got any gap display as a new row.

Below is the sample table

Emid    Fromdate    Todate
-----   --------    ------
A   1/1/2012    12/31/2012
A   12/31/2012  12/31/2013
A   12/31/2013  12/31/2014
A   2/2/2015    12/31/2099

Expected output:

emid    Fromdate    Todate
======  ========    ========
A   1/1/2012    12/31/2014
A   2/2/2015    12/31/2099

Best Answer

You could try this:

[edit] fixed a bug .. comment on line fixed [/edit]

with w_data as (
        select 'A' emid, to_date('01/01/2012','mm/dd/yyyy') fromdate, to_date('12/31/2012','mm/dd/yyyy') todate from dual union all
        select 'A' emid, to_date('12/31/2012','mm/dd/yyyy') fromdate, to_date('12/31/2013','mm/dd/yyyy') todate from dual union all
        select 'A' emid, to_date('12/31/2013','mm/dd/yyyy') fromdate, to_date('12/31/2014','mm/dd/yyyy') todate from dual union all
        select 'A' emid, to_date('02/02/2015','mm/dd/yyyy') fromdate, to_date('12/31/2099','mm/dd/yyyy') todate from dual union all
        select 'B' emid, to_date('05/12/2013','mm/dd/yyyy') fromdate, to_date('11/23/2013','mm/dd/yyyy') todate from dual union all
        select 'B' emid, to_date('11/23/2013','mm/dd/yyyy') fromdate, to_date('12/30/2013','mm/dd/yyyy') todate from dual union all
        select 'B' emid, to_date('01/10/2014','mm/dd/yyyy') fromdate, to_date('03/22/2014','mm/dd/yyyy') todate from dual union all
        select 'B' emid, to_date('04/03/2014','mm/dd/yyyy') fromdate, to_date('12/31/2099','mm/dd/yyyy') todate from dual union all
        select 'C' emid, to_date('05/12/2013','mm/dd/yyyy') fromdate, to_date('11/23/2013','mm/dd/yyyy') todate from dual union all
        select 'C' emid, to_date('11/23/2013','mm/dd/yyyy') fromdate, to_date('01/10/2014','mm/dd/yyyy') todate from dual union all
        select 'C' emid, to_date('01/10/2014','mm/dd/yyyy') fromdate, to_date('04/03/2014','mm/dd/yyyy') todate from dual union all
        select 'C' emid, to_date('04/03/2014','mm/dd/yyyy') fromdate, to_date('12/31/2099','mm/dd/yyyy') todate from dual  
        ),
  w_sub as (
     select emid, fromdate, todate,
              sum(grp) over (partition by emid order by fromdate) grp_id  -- added partition by .. forgot to group it by employee
       from (
              select emid, fromdate, todate,
                     case when fromdate = lag(todate) over (partition by emid order by fromdate) 
                       then 0 else 1 end  grp
                from w_data
             )
        )
select emid, min(fromdate) fromdate, max(todate) todate
  from w_sub
 group by emid, grp_id
 order by emid, grp_id, fromdate
/

E FROMDATE    TODATE
- ----------- -----------
A 01-jan-2012 31-dec-2014
A 02-feb-2015 31-dec-2099
B 12-may-2013 30-dec-2013
B 10-jan-2014 22-mar-2014
B 03-apr-2014 31-dec-2099
C 12-may-2013 31-dec-2099

6 rows selected.