Minimum and maximum dates within continuous date range grouped by name

Viewed 407

I have a data ranges with start and end date for a persons, I want to get the continuous date ranges only per persons:

Input:

NAME | STARTDATE      | END DATE
--------------------------------------
MIKE | **2019-05-15** | 2019-05-16 
MIKE | 2019-05-17     | **2019-05-18**
MIKE | 2020-05-18     | 2020-05-19

Expected output like:

MIKE | **2019-05-15** | **2019-05-18** 
MIKE | 2020-05-18     | 2020-05-19

So basically output is MIN and MAX for each continuous period for the person.

Appreciate any help.

I have tried the below query:

With N AS (   SELECT Name, StartDate, EndDate
       , LastStop = MAX(EndDate) 
                    OVER (PARTITION BY Name ORDER BY StartDate, EndDate 
                          ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)   FROM   Table  ), B AS (   SELECT Name, StartDate, EndDate
       , Block = SUM(CASE WHEN LastStop Is Null Then 1
                          WHEN LastStop < StartDate Then 1
                          ELSE 0
                    END)
                 OVER (PARTITION BY Name ORDER BY StartDate, LastStop)   FROM   N ) SELECT Name
     , MIN(StartDate) DateFrom
     , MAX(EndDate) DateTo FROM   B GROUP BY Name, Block ORDER BY Name, Block

But its not considering the continuous period. It's showing the same input.

3 Answers

This is a type of gap-and-islands problem. There is no need to expand the data out by day! That seems very inefficient.

Instead, determine the "islands". This is where there is no overlap -- in your case lag() is sufficient. Then a cumulative sum and aggregation:

select name, min(startdate), max(enddate)
from (select t.*,
             sum(case when prev_enddate >= dateadd(day, -1, startdate) then 0 else 1 end) over 
                 (partition by name order by startdate) as grp
      from (select t.*,
                   lag(enddate) over (partition by name order by startdate) as prev_enddate
            from t
           ) t
     ) t
group by name, grp;

Here is a db<>fiddle.

Here is an example using an ad-hoc tally table

Example or dbFiddle

;with cte as (
Select A.[Name]
      ,B.D
      ,Grp  = datediff(day,'1900-01-01',D) - dense_rank() over (partition by [Name] Order by D)
 From  YourTable A
 Cross Apply ( 
                Select Top (DateDiff(DAY,StartDate,EndDate)+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),StartDate) 
                 From  master..spt_values n1,master..spt_values n2 
             ) B

)
Select [Name]
      ,StartDate= min(D)
      ,EndDate  = max(D)
 From  cte
 Group By [Name],Grp

Returns

Name    StartDate   EndDate
MIKE    2019-05-15  2019-05-18
MIKE    2020-05-18  2020-05-19

Just to help with the Visualization, the CTE generates the following

enter image description here

This will give you the same result

    SELECT subquery.name,min(subquery.startdate),max(subquery.enddate1)
FROM (SELECT NAME,startdate,
      CASE WHEN EXISTS(SELECT yt1.startdate 
                       FROM t yt1 
                       WHERE yt1.startdate = DATEADD(day, 1, yt2.enddate) 
                       ) THEN null else yt2.enddate END as enddate1
      FROM t yt2) as subquery
GROUP by NAME, CAST(MONTH(subquery.startdate) AS VARCHAR(2)) + '-' + CAST(YEAR(subquery.startdate) AS VARCHAR(4))

For the CASE WHEN EXISTS I refered to SQL CASE

For the group by month and year you can see this GROUP BY MONTH AND YEAR

DB_FIDDLE

Related