If you have a calendar table that makes it much easier to do this. If you don't have one, you can create it like shown below. Change 20010101 to whatever start date you want to use.CREATE TABLE #Calendar(Dt date NOT NULL PRIMARY KEY CLUSTERED, RN INT);;WITH cte AS( SELECT CAST('20000101' AS DATE) AS Dt, 1 AS RN UNION ALL SELECT DATEADD(dd,1,dt), RN+1 FROM cte WHERE dt < GETDATE())INSERT INTO #Calendar SELECT dt,RN FROM cte OPTION (MAXRECURSION 0);
Once you have the calendar table, then your query against that table - but, first I want to create the test data that you posted like this:CREATE TABLE #tmp([Person Id] INT, StartDate date, EndDate date);INSERT INTO #tmp VALUES('8216548','2011-12-12',NULL),('8216548','2010-12-18','2011-11-30'),('8216548','2010-09-01','2010-11-30'),('8949999','2012-06-01',NULL),('8949999','2007-01-13','2012-05-31'),('9685324','2001-01-01','2012-05-31')
Now the query would be something like this:;WITH cte AS( SELECT t.[Person Id], c.Dt, RN-ROW_NUMBER() OVER (PARTITION BY [Person Id] ORDER BY Dt) AS Grp FROM #Calendar c CROSS JOIN (SELECT DISTINCT [Person Id] FROM #tmp ) t WHERE NOT EXISTS (SELECT * FROM #tmp t2 WHERE t2.[Person Id] = t.[Person Id] AND t2.StartDate <= c.Dt AND COALESCE(t2.EndDate,GETDATE()) > c.Dt))SELECT [Person Id], Grp, COUNT(*) GapDays, MIN(Dt) GapStart, MAX(Dt) GapEndFROM cteGROUP BY [Person Id], GrpORDER BY 1,2
Ignore that Grp column in the output - that is just for grouping gaps together.