【问题标题】:SQL Server: Rewrite recursive CTE to substitute for option maxrecursion in a ViewSQL Server:重写递归 CTE 以替换视图中的选项 maxrecursion
【发布时间】:2021-04-07 18:05:59
【问题描述】:

我遇到了一个问题,即我们不能在视图中使用下面的查询,而只能在表中使用。不幸的是,我们处理的情况是我们没有表格作为该项目的选项。

我很好奇是否有人知道我应该寻找哪个方向来代替底层逻辑:

我尝试做的是通过执行以下查询为日期范围内的每一天创建一条记录:

WITH CTE_PerDay AS (
    SELECT 
         TableDateRange.objectId
        ,TableDateRange.amount
        ,TableDateRange.beginDate
        ,COALESCE(TableDateRange.endDate, '2099-12-31') AS endDate
    FROM TableDateRange
    UNION ALL
    SELECT
         CTE_PerDay.objectId
        ,CTE_PerDay.amount
        ,DATEADD(DAY, 1, CTE_PerDay.beginDate) AS beginDate
        ,CTE_PerDay.endDate
    FROM CTE_PerDay 
    WHERE GETDATE() > DATEADD(DAY, 1, CTE_PerDay.beginDate)

)
SELECT * FROM CTE_PerDay
OPTION (maxrecursion 0)

样本数据集TableDataRange

ObjectId Amount beginDate endDate
1 500 2020-01-03
2 35 2015-05-31 2019-10-01
3 200 2017-03-15 2020-06-02
CREATE TABLE TableDateRange
(
     ObjectId   varchar(300),
     Amount     int,
     beginDate  date,
     endDate    date
);

INSERT INTO TableDateRange ( ObjectId , Amount , beginDate , endDate )
VALUES
    ('1', 500, '2020-01-03', NULL),
    ('2', 35, '2015-05-31', '2019-10-01'),
    ('3', 200, '2017-03-15', '2020-06-02');

所以查询运行良好,但是在视图中我不能使用OPTION 功能,没有它我得到错误“语句终止。在语句完成之前,最大递归 100 已用完。 有什么建议吗?

【问题讨论】:

    标签: sql tsql datetime sql-server-2012 recursive-query


    【解决方案1】:

    您可以使用计数:这是一个基于集合的解决方案,当迭代次数增加时,它的性能优于递归 - 并且在视图中得到支持。

    这是一种方法:

    select t.objectid, t.amount, dateadd(day, x.n, t.begindate) as dt
    from (
        select row_number() over (order by (select null)) - 1
        from (values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) a(n)
        cross join (values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) b(n)
        cross join (values(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) c(n)
    ) x(n)
    inner join tabledatarange t
        on dateadd(day, x.n, t.begindate) <= case 
            when enddate <= convert(date, getdate()) then enddate
            else convert(date, getdate())
        end
    

    计数生成 0 到 999 之间的所有数字(您可以通过添加 cross joins 轻松扩展它)。我们使用它来“乘”原始表的行并生成日期范围。

    我试图重写处理结束日期的部分。我知道您不希望有未来的日期,所以这就是 on 子句中的条件。

    对于这个示例数据:

    对象 ID |金额 |开始日期 |结束日期 --------: | -----: | :--------- | :--------- 1 | 500 | 2020-12-28 | 2 | 35 | 2019-09-26 | 2019-10-01 3 | 200 | 2020-05-28 | 2020-06-02

    查询返回:

    对象ID |金额 | dt --------: | -----: | :--------- 1 | 500 | 2020-12-28 1 | 500 | 2020-12-29 1 | 500 | 2020-12-30 1 | 500 | 2020-12-31 2 | 35 | 2019-09-26 2 | 35 | 2019-09-27 2 | 35 | 2019-09-28 2 | 35 | 2019-09-29 2 | 35 | 2019-09-30 2 | 35 | 2019-10-01 3 | 200 | 2020-05-28 3 | 200 | 2020-05-29 3 | 200 | 2020-05-30 3 | 200 | 2020-05-31 3 | 200 | 2020-06-01 3 | 200 | 2020-06-02

    Demo on DB Fiddle

    【讨论】:

      【解决方案2】:

      您提出的问题的答案是:将OPTION 添加到外部查询中。视图只是一个子查询,因此不能包含查询级别提示。

      您没有问的问题是:这实际上是日历表的最佳方法吗?

      答案是:不。在 CTE 中递归 28854 次对性能非常不利。

      要么在磁盘上有一个日历表,要么使用 Itzik Ben-Gan 的 tally table 更好(我可能会在表值函数中这样做):

      CREATE FUNCTION dbo.GetDates
          ( @startDate as DateTime, @endDate as DateTime )
      RETURNS TABLE WITH SCHEMABINDING
      AS RETURN
      (
      WITH
        L0 AS ( SELECT 1 AS c FROM (VALUES(1),(1)) AS D(c) ),
        L1 AS ( SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B ),
        L2 AS ( SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B ),
        L3 AS ( SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B ),
        L4 AS ( SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B ),
        L5 AS ( SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B ),
        Nums AS ( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum
                  FROM L5 )
      
      SELECT TOP (DATEDIFF(day, @startDate, @endDate) + 1)
          DATEADD(day, rownum, '1999-12-31')
      FROM Nums
      );
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-23
        • 2014-05-19
        • 1970-01-01
        • 1970-01-01
        • 2020-06-08
        • 1970-01-01
        相关资源
        最近更新 更多