如何在表值函数中为 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 选项

在表值函数中使用临时表

使用 select 语句在表值函数中传递参数

在表值函数中使用 SQL Server 2008 中的动态 SQL 语句 [重复]

jqGrid如何做字段在表全部数据中唯一性函数校验