【问题标题】:Count days in date range with set of exclusions which may overlap用一组可能重叠的排除项计算日期范围内的天数
【发布时间】:2013-03-04 21:16:03
【问题描述】:

鉴于以下示例查询,如果还给出了一组要排除的范围,考虑到这些范围可能有重叠的日期,那么计算一个日期范围内的总天数的合理且高效的方法是什么?

更简单地说,我有一个表格,其中包含一组关闭计费的日期范围,我从一个日期范围(例如 Jan1 - Jan31)开始,我需要确定在该范围内发生了多少计费天。只需将日期的日期差异减去禁用日期的日期差异的总和。但是,禁用日期范围有可能重叠,即在一条记录中禁用 Jan5-Jan8,在另一条记录中禁用 Jan7-Jan10 - 因此简单的总和会使 Jan7 加倍。排除这些重叠并获得准确计数的最佳方法是什么。

Declare @disableranges table (disableFrom datetime, disableTo datetime)
insert into @disableranges
select '01/05/2013', '01/08/2013' union
select '01/07/2013', '01/10/2013' union
select '01/15/2013', '01/20/2013'

declare @fromDate datetime = '01/01/2013'
declare @toDate datetime = '01/31/2013'

declare @totalDays int = DATEDIFF(day,@fromDate,@toDate)
declare @disabledDays int = (0 /*not sure best way to calc this*/)

select @totalDays - @disabledDays

【问题讨论】:

  • 禁用范围是否包含在内?即,01/05/201301/08/2013 会算作 3 天还是 4?默认情况下,简单地调用DATEDIFF 将包括开始日期,但不包括结束日期,因为默认时间值为0:00:00(午夜)。
  • 在我看来它会算作 3
  • 只有你知道它应该是什么:)。关键是当您说禁用范围为01/05/201301/08/2013 时,是否意味着8th 也包括在内?
  • 不,就我而言,第二个日期是“禁用”的时间,这意味着该日期不再禁用。所以第 8 个不算。

标签: sql-server tsql


【解决方案1】:

您可以使用recursive CTE 生成@dateFrom@dateTo 之间的日期。然后将日期与范围进行比较,并找到任何范围内的所有日期。最后,统计结果中的行数以获取禁用日期的计数(DEMO):

-- recursive CTE to generate dates
;with dates as (
  select @fromDate as date
  union all
  select dateadd(day, 1, date)
  from dates
  where date < @toDate
)

-- join with disable ranges to find dates in any range
, disabledDates as (
  select date from dates D
  left join @disableranges R
    on D.date >= R.disableFrom and d.Date < R.disableTo
  group by date
  having count(R.disablefrom) >= 1
)

-- count up the total disabled dates
select @disabledDays=count(*) from disabledDates;

【讨论】:

    【解决方案2】:

    就我而言,尝试过并且工作正常。

    Declare @disableranges table (disableFrom datetime, disableTo datetime)
    insert into @disableranges
    select '01/05/2013', '01/08/2013' union
    select '01/07/2013', '01/10/2013' union
    select '01/15/2013', '01/20/2013'
    
    declare @fromDate datetime = '01/01/2013'
    declare @toDate datetime = '01/31/2013'
    
    declare @totalDays int = DATEDIFF(day,@fromDate,@toDate) + 1 /*Without +1 it is giving 30 instead of 31*/
    declare @disabledDays int = (0 /*not sure best way to calc this*/)
    /*Fill temporary table with the given date range.*/
    SELECT  DATEADD(DAY, nbr - 1, @fromDate) TempDate INTO #Temp
    FROM    ( SELECT    ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS Nbr
              FROM      sys.columns c
            ) nbrs
    WHERE   nbr - 1 <= DATEDIFF(DAY, @fromDate, @toDate)
    /*Check how many dates exists in the disableranges table*/
    SELECT @disabledDays=count(*) from #Temp t WHERE 
    EXISTS(SELECT * FROM @disableranges 
    WHERE t.TempDate BETWEEN disableFrom AND DATEADD(d, -1, disableTo))
    
    select @totalDays /*Output:31*/
    select @disabledDays /*Output:10*/
    select @totalDays - @disabledDays /*Output:21*/
    drop table #Temp
    

    从答案https://stackoverflow.com/a/7825036/341117 的帮助下填写日期范围的表格

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-05
      • 1970-01-01
      • 2016-09-08
      • 2013-10-03
      • 2019-04-12
      • 1970-01-01
      相关资源
      最近更新 更多