微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决SQL Server:重写递归 CTE 以替代视图中的选项 maxrecursion

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

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

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

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,CTE_PerDay.beginDate)

)
SELECT * FROM CTE_PerDay
OPTION (maxrecursion 0)

样本数据集 TableDatarange

对象 ID 金额 开始日期 结束日期
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 在语句完成之前已用完。 有什么建议吗?

解决方法

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

这是一种方法:

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)) a(n)
    cross join (values(0),(0)) b(n)
    cross join (values(0),(0)) c(n)
) x(n)
inner join tabledatarange t
    on dateadd(day,t.begindate) <= case 
        when enddate <= convert(date,getdate()) then enddate
        else convert(date,getdate())
    end

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

我试图重写处理结束日期的部分。我知道您不想要未来的日期,因此 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

查询返回:

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

,

您提出的问题的答案是:将 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
);

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。