【问题标题】:How to set the maxrecursion option for a CTE inside a Table-Valued-Function如何在表值函数中为 CTE 设置 maxrecursion 选项
【发布时间】: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 选项的情况下运行正常 但该选项存在语法错误。 解决办法是什么?

【问题讨论】:

    标签: sql-server common-table-expression


    【解决方案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
           /*)*/
          

          【讨论】:

          • 请更正代码格式并解释一下它的作用...
          猜你喜欢
          • 2011-11-17
          • 1970-01-01
          • 2013-02-11
          • 2012-11-03
          • 2021-04-07
          • 1970-01-01
          • 2020-12-29
          • 1970-01-01
          • 2016-11-24
          相关资源
          最近更新 更多