如何为表值函数内的 CTE 设置 maxrecursion 选项

发布于 2024-12-04 11:13:57 字数 796 浏览 0 评论 0原文

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

I'm facing a problem declaring the maxrecursion option for a CTE inside a TVF.

Here is the CTE (a simple calendar):

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)

and the 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)
 )

The above TVF is running OK without the maxrecursion option
but there is a syntax error with the option.
What is the solution?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

这个 MSDN 论坛帖子我了解到

[the] OPTION 子句只能在语句级别使用

因此,您不能在视图定义或内联 TVF 等内的查询表达式中使用它。在您的情况下使用它的唯一方法是创建不带 OPTION 子句的 TVF 并在使用 TVF 的查询。我们有一个错误,该错误跟踪允许在任何查询表达式(例如,ifexists() 或 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 子句只能在语句级别使用”,这是最终的(目前)。

From this MSDN forums thread I learn that

[the] OPTION clause can be used only at the statement level

So you cannot use it within a query expression inside view definitions or inline TVFs etc. The only way to use it in your case is to create the TVF without the OPTION clause and specify it in the query that uses the TVF. We have a bug that tracks request for allowing use of OPTION clause inside any query expression (for example, if exists() or CTE or view).

and further

You can not change the default value of that option inside a udf. You
will have to do it in the statement referencing the udf.

So in your example, you must specify the OPTION when you call your function:

 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
 )

(later)

SELECT * FROM [liste_jour] ( @from , @to ) OPTION ( MAXRECURSION 365 )

Note that you can't work round this by having a second TVF that just does the above line - you get the same error, if you try. "[the] OPTION clause can be used only at the statement level", and that's final (for now).

对不⑦ 2024-12-11 11:13:57

老线程,我知道,但我需要同样的东西,只是通过使用多语句 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

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

Old thread, I know, but I needed the same thing and just dealt with it by using a multi-statement 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

There are probably efficiency issues with this, but I can afford it in my case.

那伤。 2024-12-11 11:13:57

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

A little creative use of CTEs and cartesian products (cross joins) will get you around the MAXRECURSION limit of 100. 3 CTEs with a limit of 4 records on the last one nets you 40,000 records, which will be good for more than 100 years worth of data. If you expect more difference between @debut and @fin, you can adjust 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')
惯饮孤独 2024-12-11 11:13:57

老问题但是......我只是想澄清为什么在内联表值函数中不允许使用 OPTION(MAXRECURSION x) 。这是因为当您在查询中使用它们时,iTVF 会内联。而且,众所周知,您不能将此选项放在查询最后的任何其他位置。这就是永远不可能将其放入 iTVF 中的原因(除非解析器和/或代数器在幕后做了一些魔法,我认为它不会很快实现)。 mTVF(多语句表值函数)是一个不同的故事,因为它们不会内联(并且速度太慢,以至于永远不应该在查询中使用它们;不过,在对变量的赋值中使用它们是可以的,但是再说一次——小心循环!)。

Old issue but... I just wanted to clarify why OPTION(MAXRECURSION x) is not allowed in an in-line table-valued function. This is because iTVF's get inlined when you use them in a query. And, as we all know, you cannot put this option anywhere else save at the very end of the query. This is THE reason it will never be possible to put it inside an iTVF (unless the parser and/or algebrizer does some magic behind the scenes, which I don't think it will any time soon). mTVF's (multi-statement table-valued functions) is a different story because they don't get inlined (and are so slow that they should never be used in queries; it's OK to use them in an assignment to a variable, though, but then again---beware of loops!).

我恋#小黄人 2024-12-11 11:13:57

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

Another way to handle this is to break up the problem into a pair of CTEs, neither of which hits the recursion limit of 100. The first CTE creates a list with the begin date for each month in the range. The second CTE then fills in all the days of each month. As long as the input range is fewer than 100 months, it should work fine. If an input range of greater than 100 months is required, the same idea could be expanded with a third CTE for years added ahead of the months 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
)
五里雾 2024-12-11 11:13:57

为您创建简单的示例:)

/* 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
 /*)*/

create simple sample for you :)

/* 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
 /*)*/
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文