如何在Table-Valued-Function中为CTE设置maxrecursion选项

时间:2011-09-15 09:27:37

标签: sql-server common-table-expression

我在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选项的情况下运行正常 但是该选项存在语法错误。 解决方案是什么?

问候

6 个答案:

答案 0 :(得分:40)

this MSDN forums thread我了解到

  

[{1]}子句只能在语句级别使用

     

因此,您无法在视图定义或内联TVF内的查询表达式中使用它。在您的情况下使用它的唯一方法是创建没有OPTION子句的TVF并在使用的查询中指定它TVF。我们有一个错误跟踪允许在任何查询表达式中使用OPTION子句的请求(例如,OPTION或CTE或视图)。

并进一步

  

您无法在udf中更改该选项的默认值。您   必须在引用udf的语句中进行。

因此,在您的示例中,当您调用您的函数时,您必须指定if exists()

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
 )

请注意,你不能通过让第二个TVF执行上述操作来解决这个问题 - 如果你尝试,你会得到同样的错误。 “[{] SELECT * FROM [liste_jour] ( @from , @to ) OPTION ( MAXRECURSION 365 ) 子句只能在语句级别使用”,这是最终的(现在)。

答案 1 :(得分:22)

旧线程,我知道,但我需要同样的事情,只需使用多语句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

这可能存在效率问题,但我可以负担得起。

答案 2 :(得分:2)

处理此问题的另一种方法是将问题分解为一对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
)

答案 3 :(得分:2)

旧问题但是......我只是想澄清为什么OPTION(MAXRECURSION x)不允许在内联表值函数中。这是因为在查询中使用iTVF的内联。而且,众所周知,除了查询的最后,你不能把这个选项放在其他任何地方。这是 的原因,永远不可能把它放在iTVF中(除非解析器和/或algebrizer在幕后做了一些魔术,我认为不会很快)。 mTVF(多语句表值函数)是一个不同的故事,因为它们没有内联(并且速度很慢,以至于它们永远不应该在查询中使用;但是可以在赋值给变量时使用它们,但是然后再次 - 小心循环!)。

答案 4 :(得分:0)

对CTE和笛卡尔产品(交叉连接)的一点创造性使用将使您在MAXRECURSION限制为100左右。最后一个限制为4条记录的CTE将为您提供40,000条记录,这将是有利于超过100年的数据。如果您希望@debut和@fin之间存在更多差异,则可以调整cte3。另外,请停止SHOUTING您的SQL。

-- 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')

答案 5 :(得分:0)

为您创建简单的示例:)

/ *块创建功能以在sql * /中进行测试 / * FUNCTION [fn_CTE_withLevel](@max_level int) 退货表   如  返回
       (* /

/ *******************声明表只替换真实表***** /

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');

/ *******************声明表只替换真实表***** /

  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)

-取消对创建功能的注释  / /