Как установить параметр maxrecursion для CTE внутри функции с табличными значениями

У меня проблема с объявлением опции maxrecursion для CTE внутри TVF.

Вот 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

Из этого форума форума MSDN Я узнаю, что

Предложение

[the] OPTION может использоваться только на уровне инструкции

Поэтому вы не можете использовать его внутри выражения запроса внутри определений представления или встроенных TVF и т.д. Единственный способ использовать его в вашем случае - создать TVF без предложения OPTION и указать его в запросе, использующем TVF, У нас есть ошибка, которая отслеживает запрос, позволяющий использовать предложение OPTION внутри любого выражения запроса (например, if exists() или CTE или view).

и далее

Вы не можете изменить значение по умолчанию этого параметра внутри 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

Вероятно, с этим связаны проблемы с эффективностью, но я могу себе это позволить в своем случае.

Ответ 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 получает встроенный, когда вы используете их в запросе. И, как мы все знаем, вы не можете поместить эту опцию в другое место, кроме как в самом конце запроса. Это причина, по которой он никогда не сможет быть помещен в iTVF (если парсер и/или алгебраист не совершают какую-то магию за кулисами, что я не думаю, что это скоро произойдет). mTVF (многозначные табличные функции) - это другая история, потому что они не получают встроенных (и настолько медленны, что их никогда не следует использовать в запросах, но использовать их в присваивании переменной, но затем снова --- остерегайтесь циклов!).

Ответ 5

Небольшое творческое использование CTE и декартовых произведений (перекрестные объединения) поможет вам преодолеть ограничение MAXRECURSION в 100. 3 CTE с ограничением в 4 записи на последнем дают вам 40000 записей, что будет хорошо для более чем 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')

Ответ 6

создать простой образец для вас :)

/* блокировать функцию создания для теста в 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)

- раскомментировать для создания функции /)/