SQL 服务器:重写递归 CTE 以替代视图中的选项 maxrecursion

SQL Server: Rewrite recursive CTE to substitute for option maxrecursion in a View

我 运行 解决了我们不能在视图中使用底层查询的问题,但只能在 table 中使用。不幸的是,我们处理的情况是我们没有 tables 作为该项目的选项。

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

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

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 功能,没有它我会得到错误 'The statement terminated. The maximum recursion 100 has been exhausted before statement completion.' 有什么建议吗?

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

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

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

最好是在磁盘上有一个日历 table,或者使用 Itzik Ben-Gan 的 tally table(我可能会在 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
);

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

这里有一个方法:

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 join 轻松扩展它)。我们用它来“乘以”原始 table 的行并生成日期范围。

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

对于此示例数据:

ObjectId | Amount | beginDate  | endDate   
-------: | -----: | :--------- | :---------
       1 |    500 | 2020-12-28 | null      
       2 |     35 | 2019-09-26 | 2019-10-01
       3 |    200 | 2020-05-28 | 2020-06-02

查询returns:

objectid | amount | 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