Вот решение, в котором используется идея Tally table (о которой я впервые услышал в статье Ицка Бен-Гана - я все еще вырезаю и вставляю его код всякий раз, когда поднимается тема).Идея состоит в том, чтобы сгенерировать список возрастающих целых чисел, объединить исходные данные по диапазону с числами, а затем подсчитать количество различных чисел следующим образом.(Этот код использует синтаксис SQL Server 2008, но с незначительными изменениями будет работать в SQL 2005.)
Сначала настройте некоторые данные тестирования:
CREATE TABLE #EventTable
(
PersonId int not null
,startDate datetime not null
,endDate datetime not null
)
INSERT #EventTable
values (1, 'Jan 1, 2011', 'Jan 4, 2011')
,(1, 'Jan 3, 2011', 'Jan 5, 2011')
,(2, 'Jan 1, 2011', 'Jan 3, 2011')
,(2, 'Jan 6, 2011', 'Jan 9, 2011')
Определите некоторые начальные значения
DECLARE @Interval bigint, @ FirstDay datetime, @ PersonId int = 1 - (или что-то еще)
Получите первый день и максимально возможное количество дат (чтобы сохранить cte от генерирования дополнительных значений):
SELECT
@Interval = datediff(dd, min(startDate), max(endDate)) + 1
,@FirstDay = min(startDate)
from #EventTable
where PersonId = @PersonId
Вырежьте и вставьте одну подпрограмму, измените и протестируйте ее, чтобы она возвращала столько целых чисел, сколько нам потребуется:
/*
;WITH
Pass0 as (select 1 as C union all select 1), --2 rows
Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
Pass5 as (select 1 as C from Pass4 as A, Pass4 as B),--4,294,967,296 rows
Tally as (select row_number() over(order by C) as Number from Pass5)
select Number from Tally where Number <= @Interval
*/
А теперь исправьте ее, сначала присоединившисьдо интервалов, определенных в каждой строке источника, а затем подсчитайте каждое найденное отдельное значение:
;WITH
Pass0 as (select 1 as C union all select 1), --2 rows
Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
Pass5 as (select 1 as C from Pass4 as A, Pass4 as B),--4,294,967,296 rows
Tally as (select row_number() over(order by C) as Number from Pass5)
SELECT PersonId, count(distinct Number) EventDays
from #EventTable et
inner join Tally
on dateadd(dd, Tally.Number - 1, @FirstDay) between et.startDate and et.endDate
where et.PersonId = @PersonId
and Number <= @Interval
group by PersonId
Выньте фильтр @PersonId
, и вы получите его для всех людей.А с небольшими изменениями вы можете сделать это за любой промежуток времени, а не только за дни (именно поэтому я настроил таблицу Tally для генерации очень больших чисел.)