How to merge time intervals in SQL Server

前端 未结 7 1706
再見小時候
再見小時候 2021-01-03 01:43

Suppose I have the following an event table with personId, startDate and endDate.

I want to know how much time the person X sp

相关标签:
7条回答
  • 2021-01-03 02:40

    Here's a solution that uses the Tally table idea (which I first heard of in an article by Itzk Ben-Gan -- I still cut and paste his code whenver the subject comes up). The idea is to generate a list of ascending integers, join the source data by range against the numbers, and then count the number of distinct numbers, as follows. (This code uses syntax from SQL Server 2008, but with minor modifications would work in SQL 2005.)

    First set up some testing data:

    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')
    

    Determine some initial values

    DECLARE @Interval bigint ,@FirstDay datetime ,@PersonId int = 1 -- (or whatever)

    Get the first day and the maximum possible number of dates (to keep the cte from generating extra values):

    SELECT
       @Interval = datediff(dd, min(startDate), max(endDate)) + 1
      ,@FirstDay = min(startDate)
     from #EventTable
     where PersonId = @PersonId
    

    Cut and paste over the one routine and modify and test it to only return as many integers as we'll need:

    /*
    ;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
    */
    

    And now revise it by first joining to the intervals defined in each source row, and then count each distinct value found:

    ;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
    

    Take out the @PersonId filter and you'd get it for all persons. And with minor modification you can do it for any time interval, not just days (which is why I set the Tally table to generate severely large numbers.)

    0 讨论(0)
提交回复
热议问题