如何在表值函数中为 CTE 设置 maxrecursion 选项
Posted
技术标签:
【中文标题】如何在表值函数中为 CTE 设置 maxrecursion 选项【英文标题】:How to set the maxrecursion option for a CTE inside a Table-Valued-Function 【发布时间】:2011-11-17 17:23:40 【问题描述】:我在为 TVF 中的 CTE 声明 maxrecursion 选项时遇到问题。
这是 CTE(一个简单的日历):
DECLARE @DEBUT DATE = '1/1/11', @FIN DATE = '1/10/11';
WITH CTE as(
SELECT @debut as jour
UNION ALL
SELECT DATEADD(day, 1, jour)
FROM CTE
WHERE DATEADD(day, 1, jour) <= @fin)
SELECT jour FROM CTE option (maxrecursion 365)
和 TVF:
CREATE FUNCTION [liste_jour]
(@debut date,@fin date)
RETURNS TABLE
AS
RETURN
(
WITH CTE as(
SELECT @debut as jour
UNION ALL
SELECT DATEADD(day, 1, jour)
FROM CTE
WHERE DATEADD(day, 1, jour) <= @fin)
SELECT jour FROM CTE
--option (maxrecursion 365)
)
上面的 TVF 在没有 maxrecursion 选项的情况下运行正常 但该选项存在语法错误。 解决办法是什么?
【问题讨论】:
【参考方案1】:从this MSDN forums thread我了解到
[the]
OPTION
子句只能在语句级别使用因此,您不能在视图定义或内联 TVF 等内的查询表达式中使用它。在您的情况下使用它的唯一方法是创建没有
OPTION
子句的 TVF,并在使用 TVF 的查询中指定它.我们有一个错误会跟踪允许在任何查询表达式(例如,if exists()
或 CTE 或视图)中使用OPTION
子句的请求。
还有更多
您不能在 udf 中更改该选项的默认值。你 必须在引用 udf 的语句中执行此操作。
因此,在您的示例中,您必须在 调用您的函数时指定 OPTION
:
CREATE FUNCTION [liste_jour]
(@debut date,@fin date)
RETURNS TABLE
AS
RETURN
(
WITH CTE as(
SELECT @debut as jour
UNION ALL
SELECT DATEADD(day, 1, jour)
FROM CTE
WHERE DATEADD(day, 1, jour) <= @fin)
SELECT jour FROM CTE -- no OPTION here
)
(稍后)
SELECT * FROM [liste_jour] ( @from , @to ) OPTION ( MAXRECURSION 365 )
请注意,您无法通过使用第二个 TVF 来解决此问题 - 如果您尝试,您会得到相同的错误。 “[the] OPTION
子句只能在语句级别使用”,这是最终的(目前)。
【讨论】:
【参考方案2】:旧线程,我知道,但我需要同样的东西,只是通过使用多语句 UDF 来处理它:
CREATE FUNCTION DatesInRange
(
@DateFrom datetime,
@DateTo datetime
)
RETURNS
@ReturnVal TABLE
(
date datetime
)
AS
BEGIN
with DateTable as (
select dateFrom = @DateFrom
union all
select DateAdd(day, 1, df.dateFrom)
from DateTable df
where df.dateFrom < @DateTo
)
insert into @ReturnVal(date)
select dateFrom
from DateTable option (maxrecursion 32767)
RETURN
END
GO
这可能存在效率问题,但我可以负担得起。
【讨论】:
这个答案可能会好一点,因为函数中不需要记住CTE(没有递归限制)。 当然可以解决问题,但它“可能有个缺点”,因为这个表值函数是non-inlined
。就性能而言,它可能很重要。
正如我所说,“这可能存在效率问题,但我可以负担得起。”【参考方案3】:
解决此问题的另一种方法是将问题分解为一对 CTE,它们都没有达到 100 的递归限制。第一个 CTE 创建一个列表,其中包含范围内每个月的开始日期。然后第二个 CTE 填写每个月的所有日期。只要输入范围小于 100 个月,它应该可以正常工作。如果需要大于 100 个月的输入范围,则可以通过在 CTE 月份之前添加第三个 CTE 来扩展相同的想法。
CREATE FUNCTION [liste_jour]
(@debut datetime, @fin datetime)
RETURNS TABLE
AS
RETURN
(
WITH CTE_MOIS AS
(
SELECT JOUR_DEBUT = @debut
UNION ALL
SELECT DATEADD(MONTH, 1, CTE_MOIS.JOUR_DEBUT)
FROM CTE_MOIS
WHERE DATEADD(MONTH, 1, CTE_MOIS.JOUR_DEBUT) <= @fin
),
CTE_JOUR AS
(
SELECT JOUR = CTE_MOIS.JOUR_DEBUT
FROM CTE_MOIS
UNION ALL
SELECT DATEADD(DAY, 1, CTE_JOUR.JOUR)
FROM CTE_JOUR
WHERE MONTH(CTE_JOUR.JOUR) = MONTH(DATEADD(DAY, 1, CTE_JOUR.JOUR)) AND
DATEADD(DAY, 1, CTE_JOUR.JOUR) <= @FIN
)
SELECT JOUR
FROM CTE_JOUR
)
【讨论】:
【参考方案4】:老问题但是...我只是想澄清为什么内联表值函数中不允许使用OPTION(MAXRECURSION x)
。这是因为当您在查询中使用 iTVF 时,它们会内联。而且,众所周知,您不能将此选项放在查询末尾以外的任何其他位置。这是 THE 永远不可能将其放入 iTVF 的原因(除非解析器和/或代数器在幕后做了一些魔术,我认为不会很快)。 mTVF(多语句表值函数)是另一回事,因为它们不会被内联(而且速度太慢以至于永远不应该在查询中使用它们;不过,可以在对变量的赋值中使用它们,但是再说一遍——小心循环!)。
【讨论】:
【参考方案5】:稍微创造性地使用 CTE 和笛卡尔积(交叉连接)将使您绕过 MAXRECURSION
100 条的限制。最后一个限制为 4 条记录的 3 个 CTE 可以为您带来 40,000 条记录,这将有利于超过 100 年的数据。如果您期望@debut 和@fin 之间有更多差异,您可以调整cte3
。
-- please don't SHOUTCASE your SQL anymore... this ain't COBOL
alter function liste_jour(@debut date, @fin date) returns table as
return (
with cte as (
select 0 as seq1
union all
select seq1 + 1
from cte
where seq1 + 1 < 100
),
cte2 as (
select 0 as seq2
union all
select seq2 + 1
from cte2
where seq2 + 1 < 100
),
cte3 as (
select 0 as seq3
union all
select seq3 + 1
from cte3
where seq3 + 1 <= 3 -- increase if 100 years isn't good enough
)
select
dateadd(day, (seq1 + (100 * seq2) + (10000 * seq3)), @debut) as jour
from cte, cte2, cte3
where (seq1 + (100 * seq2) + (10000 * seq3)) <= datediff(day, @debut, @fin)
)
go
-- test it!
select * from liste_jour('1/1/2000', '2/1/2000')
【讨论】:
我同意please don't SHOUTCASE your SQL anymore... this ain't COBOL
评论?【参考方案6】:
为您创建简单的示例:)
/* block create function for test in sql*/
/*FUNCTION [fn_CTE_withLevel] (@max_level int)
RETURNS TABLE
AS
RETURN
( */
/******************* declare table just replace real table *****/
declare @tbl table(pid varchar(15),id varchar(15))
/* use function argument */
declare @max_level int = 3
Insert Into @tbl(pid , id)
values
/*lev1*/ ('0','1') ,
/*lev2*/ ('1','101') ,
/*lev2*/ ('1','102') ,
/*lev1*/ ('0','2') ,
/*lev2*/ ('2','201') ,
/*lev3*/ ('201','20101') ,
/*lev3*/ ('201','20102') ,
/*lev2*/ ('2','202') ,
/*lev1*/ ('0','3') ,
/*lev2*/ ('3','301') ,
/*lev2*/ ('3','302') ,
/*lev1*/ ('0','4') ,
/*lev2*/ ('4','401'),
/*lev2*/ ('4','402');
/******************* declare table just replace real table *****/
With cte_result(pid , id , lev)
As(
Select pid , id , 1 as lev From @tbl t
Where pid = '0' /* change to another values from list to test sub items */
Union All
Select t.pid , t.id , cte.lev + 1 as lev
From cte_result cte
inner Join @tbl t
On cte.id = t.pid
Where cte.lev < @max_level -- :) this is my idea
)
Select * From cte_result
--OPTION (MAXRECURSION 100)
-- uncomment for create function
/*)*/
【讨论】:
请更正代码格式并解释一下它的作用...以上是关于如何在表值函数中为 CTE 设置 maxrecursion 选项的主要内容,如果未能解决你的问题,请参考以下文章
sql 表表达式 - 视图,临时表,表变量,派生表,表值函数,公用表表达式(CTE)
如何在 Table-Valued-Function 中为 CTE 设置 maxrecursion 选项