How to set the maxrecursion option for a CTE inside a Table-Valued-Function

后端 未结 6 1488
粉色の甜心
粉色の甜心 2020-11-29 08:01

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


        
相关标签:
6条回答
  • 2020-11-29 08:23

    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
     /*)*/
    
    0 讨论(0)
  • 2020-11-29 08:26

    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.

    0 讨论(0)
  • 2020-11-29 08:26

    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
    )
    
    0 讨论(0)
  • 2020-11-29 08:37

    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!).

    0 讨论(0)
  • 2020-11-29 08:39

    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')
    
    0 讨论(0)
  • 2020-11-29 08:44

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

    0 讨论(0)
提交回复
热议问题