sqlsql-serverdatedate-rangebetween

Find missing date ranges between two dates


I have table which having data of some date ranges. when user select start date and end date then the result set will be like all date ranges between that 2 dates also all missing date ranges between that 2 dates.
For Example:
DateRangesTable

ID| fromdate  | todate    |
----------------------------
1 | 5-May-21  | 10-May-21 |
2 | 17-May-21 | 25-May-21 |

this is my main table ,I mention below all result set which I wanted with above table

if user select : 5-May-2021 to 25-May-2021
Expected Result :

ID| fromdate  | todate    |
----------------------------
1 | 5-May-21  | 10-May-21 |
0 | 11-May-21 | 16-May-21 |
2 | 17-May-21 | 25-May-21 |

if user select : 6-May-2021 to 23-May-2021
Expected Result :

ID| fromdate  | todate    |
-----------------------------
1 | 6-May-21  | 10-May-21 |
0 | 11-May-21 | 16-May-21 |
2 | 17-May-21 | 23-May-21 |

if user select : 1-May-2021 to 28-May-2021
Expected Result :

ID| fromdate  | todate    |
----------------------------
1 | 1-May-21  | 4-May-21  |
1 | 5-May-21  | 10-May-21 |
0 | 11-May-21 | 16-May-21 |
2 | 17-May-21 | 25-May-21 |
2 | 26-May-21 | 28-May-21 |

Here some question which is not to similar but try to find:

SQL Find missing date ranges

SQL how to write a query that return missing date ranges?

Thanks in advance.


Solution

  • A note, I am assuming here that the expected results for your final expected results is wrong here as it doesn't match the other 2. The last and first rows in the expected results for the last set both have a value for ID that isn't 0, but no explanation of why they do is given. I therefore assume the value should be 0 like the row in the "middle".

    To do this, I use a Tally to get all the dates between the date range you need; the Tally is limited to 1,000 rows, a little shy of 3 years, but you can cross join to N more if you need more rows. I then use that tally to create an inline calendar table. Next I LEFT JOIN that calendar to your data, and use a gaps and island method to put the values into groups. Finally I then aggregate on those groups, getting the MIN and MAX date in each one:

    USE Sandbox;
    GO
    
    CREATE TABLE dbo.YourTable (ID int,
                                FromDate date,
                                ToDate date);
    INSERT INTO dbo.YourTable
    VALUES(1,'20210505','20210510'),
          (2,'20210517','20210525');
    GO
    
    DECLARE @StartDate date = '20210501',
            @EndDate date = '20210528';
    
    WITH N AS(
        SELECT N
        FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
    Tally AS(
        SELECT 0 AS I
        UNION ALL
        SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate))
               ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
        FROM N N1, N N2, N N3), --1000 days
    Dates AS(
        SELECT DATEADD(DAY, T.I, @StartDate) AS [Date],
               T.I
        FROM Tally T),
    Grps AS(
        SELECT D.[Date],
               YT.ID,
               D.I - ROW_NUMBER() OVER (PARTITION BY ID ORDER BY D.[Date]) AS Grp
        FROM Dates D
             LEFT JOIN dbo.YourTable YT ON D.[Date] >= YT.FromDate AND D.[Date] <= YT.ToDate)
    SELECT ISNULL(MAX(G.ID),0) AS ID,
           MIN(G.[Date]) AS FromDate,
           MAX(G.[Date]) AS ToDate
    FROM Grps G
    GROUP BY G.Grp
    ORDER BY FromDate ASC;
    
    GO
    DROP TABLE dbo.YourTable;
                              
    

    db<>fiddle