Search code examples
sqlsql-servert-sqldateoverlap

How to get seperate intervals from overlapping dates


I have table which has start and end dates, such as below

drop table if exists #Temp
select * 
into #Temp
from (values (1, 1, '2011-10-09', '2011-10-20'),
             (2, 1, '2011-10-14', '2011-10-30'),
             (3, 1, '2012-05-21', '2012-05-25')
     ) as value (id, userId, d1, d2)

which gives me this initial table:

id  userId    d1            d2
1   1         2011-10-09    2011-10-20
2   1         2011-10-14    2011-10-30
3   1         2012-05-21    2012-05-25

Problem: What I'm trying to achieve is to get 3 intervals out of this 2 overlapping dates. How to achieve that?

select * 
from 
    #Temp t1
    join #Temp t2
        on t1.userId = t2.userId
        and t1.id != t2.id
where
    t1.d1 <= t2.d2
    and t1.d2 >= t2.d1
order by t1.d1

gives me

id  userId    d1            d2              id  userId    d1            d2
1   1         2011-10-09    2011-10-20      2   1         2011-10-14    2011-10-30
2   1         2011-10-14    2011-10-30      1   1         2011-10-09    2011-10-20  

I'm not sure how to continue from here.

What I want to achieve is the following table where the overlaps are seperated.

Expected Result:

userId  d1          d2              
1       2011-10-09  2011-10-14
1       2011-10-14  2011-10-20
1       2011-10-20  2011-10-30
1       2012-05-21  2012-05-25

Solution

  • One way will be to unpivot the data, then order the dates and then again pivot it excluding some of the records.

    WITH dataSource AS
    (
        select id, userId, d1
        from #Temp t1
        UNION ALL
        select id, userId, d2
        from #Temp t1
    ), DataSourceOrdered AS
    (
        SELECT 
            id, userId, d1,
            row_number () OVER (PARTITION BY userId ORDER By d1) as rowid
        FROM dataSource DS1
    )
    SELECT *
    FROM DataSourceOrdered ds1
    INNER JOIN DataSourceOrdered ds2
        ON ds1.userId = ds2.[userId]
        and ds1.[rowid] + 1 = ds2.[rowid]
        and exists (
            SELECT 1 
            FROM #Temp x 
            WHERE ds1.[d1] >= x.d1 and ds1.[d1] < x.d2 and ds1.userId = x.userId
        )
    

    It may need to be changed a little depending on your real data, but with the current one, it seems to work.

    enter image description here