【问题标题】:Can a recursive query help in this date period "negation" task递归查询能否在此日期期间“否定”任务中提供帮助
【发布时间】:2013-06-02 15:49:44
【问题描述】:

我正在研究某种形式的内联(即不是 UDF os SPROC)选择,它将返回的日期范围转换为“负”日期范围的形式。这是“源”集:

SELECT id, SDate, EDate
FROM table
ORDER BY SDate

样本集:

1  01/05/2013  01/12/2013
2  01/13/2013  01/18/2013
3  02/05/2013  03/12/2013
4  07/08/2013  08/31/2013

寻求的结果(假设我们只对 2013 年感兴趣)

01/01/2013 01/04/2013
01/19/2013 02/04/2013
03/13/2013 07/07/2013
09/01/2013 12/31/2013

我在尝试使用 CTE 进行递归查询时是否走在正确的轨道上?谁有可以分享的SQL?

【问题讨论】:

  • 我的简短回答中缺少什么?

标签: sql-server tsql common-table-expression recursive-query


【解决方案1】:
declare @t table (SDate date, Edate date)
insert @t values ('2013-01-01', '2013-12-20')

declare @year int = 2013

;with dates as
(
select dateadd(year, @year - 1900, 0) d
union all
select d + 1
from dates where
d < dateadd(year, @year - 1899, -1)
),
b as
(
select d, grp = d-row_number() over (order by d) from dates 
where not exists (select 1 from @t where dates.d between SDate and EDate)
)
select min(d), max(d) from b group by grp
option (maxrecursion 366)

【讨论】:

    【解决方案2】:

    以下处理反转日期范围并正确处理与目标范围的开始和/或结束重叠的范围。 编辑:已更新以处理未找到适用范围的情况。

    declare @DateRanges as Table ( Id Int Identity, SDate Date, EDate Date );
    insert into @DateRanges ( SDate, EDate ) values
      ( '20130105', '20130112' ),
      ( '20130113', '20130118' ),
      ( '20130205', '20130312' ),
      ( '20130708', '20130831' ),
      -- Additional test data for ranges that overlap the target date range, or not.  
      ( '20100101', '20130101' ),
      ( '20131127', '20140102' ),
      ( '19990208', '20041118' ),
      ( '20601113', '20990101' );
    select * from @DateRanges order by SDate;
    
    declare @StartDate as Date = '20130101';
    declare @EndDate as Date = DateAdd( day, -1, DateAdd( year, 1, @StartDate ) );
    select @StartDate as [@StartDate], @EndDate as [@EndDate];
    
    with SortedDateRanges as (
      -- Sort the date ranges so that we have a dense row number for later use.
      select SDate, EDate, Row_Number() over ( order by SDate ) as RN
        from @DateRanges
        where EDate >= @StartDate and SDate <= @EndDate ),
      PairedDateRanges as (
      -- Pair the adjacent date ranges and turn them inside out.
      select DateAdd( day, 1, L.EDate ) as SDate, DateAdd( day, -1, R.SDate ) as EDate
        from SortedDateRanges as L inner join
          SortedDateRanges as R on R.RN = L.RN + 1 )
      -- The result is all of inside out date ranges that are valid, i.e. don't end before they start...
      select SDate, EDate, 'Fill Gap' as Reason
        from PairedDateRanges
        where SDate <= EDate
      union
      --  ... plus any leading date range ...
      select @StartDate, DateAdd( day, -1, SDate ), 'Leading'
        from SortedDateRanges
        where RN = 1 and SDate > @StartDate
      union  
      --  ... plus any trailing date range ...
      select DateAdd( day, 1, EDate ), @EndDate, 'Trailing'
        from SortedDateRanges
        where RN = ( select Max( RN ) from SortedDateRanges ) and EDate < @EndDate
      union
      -- ... or we have nothing.
      select @StartDate, @EndDate, 'No Data'
        where not exists ( select 42 from SortedDateRanges )
      order by SDate;
    

    【讨论】:

    • 非常类似于我要去的地方。感谢您为我节省时间!!
    • 您能否更新您的答案(为了完整性)以在源集为空时包含边缘情况。我认为这样做:-- ... plus any leading date range ... SELECT @PeriodSDate, CASE WHEN cal.RN IS NULL THEN @PeriodEDate ELSE DateAdd( day, -1, cal.start_date ) END, 'Leading' FROM (SELECT @PeriodSDate AS start_date, @PeriodEDate AS end_date) AS def LEFT OUTER JOIN SortedDateRanges cal ON EXISTS(SELECT * FROM SortedDateRanges) WHERE (RN = 1 AND cal.start_date &gt; @PeriodSDate) OR (cal.RN IS NULL)
    • @G.Stoynev - 答案已更新以处理空输入表或目标范围内没有日期范围的表。似乎最简单的方法是添加另一个从无处选择的UNION
    • 没有 t-clausen.dk 的回答那么优雅,但我想这个更容易理解。
    • 我不会说它不那么优雅——“将相邻范围从内到外”的技巧非常巧妙。没有任何速度测试,这个看起来有更少的开销——没有分组也没有 EXISTS。两者都保留,以便我有时间在负载下尝试它们。
    猜你喜欢
    • 1970-01-01
    • 2011-02-02
    • 1970-01-01
    • 1970-01-01
    • 2022-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多