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
我 运行 解决了我们不能在视图中使用底层查询的问题,但只能在 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