Как настроить параметр maxrecursion для CTE внутри табличной функции - PullRequest
36 голосов
/ 15 сентября 2011

У меня проблема с объявлением опции 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 но есть синтаксическая ошибка с опцией. какое решение?

1011 * привет *

Ответы [ 6 ]

41 голосов
/ 15 сентября 2011

Из этой ветки форумов 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, который просто выполняет вышеприведенную строку - вы получите ту же ошибку, если попытаетесь. «[*] OPTION предложение может использоваться только на уровне оператора», и это окончательно (на данный момент).

22 голосов
/ 07 июня 2012

Старый поток, я знаю, но мне нужно было то же самое, и я справился с этим, используя 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 голосов
/ 21 сентября 2015

Старая проблема, но ... Я просто хотел уточнить, почему OPTION (MAXRECURSION x) не разрешено в встроенной табличной функции. Это потому, что iTVF становится встроенным , когда вы используете их в запросе. И, как мы все знаем, вы не можете поместить эту опцию где-либо еще, кроме как в самом конце запроса. Это THE причина, по которой никогда не будет возможности поместить его в iTVF (если парсер и / или algebrizer не сотворит магию за кулисами, что я не думаю, что это произойдет в ближайшее время). mTVF (табличные функции с несколькими утверждениями) - это отдельная история, потому что они не становятся встроенными (и настолько медленны, что их никогда не следует использовать в запросах; все же можно использовать их в присваивании переменной, но потом опять --- остерегайся петель!).

2 голосов
/ 26 августа 2015

Другой способ справиться с этим - разбить проблему на пару 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
)
0 голосов
/ 02 июля 2018

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

/ * блок создания функции для теста в sql * / / * FUNCTION [fn_CTE_withLevel] (@max_level int) ВОЗВРАЩАЕТСЯ СТОЛ КАК RETURN
(* /

/ ******************* объявлять таблицу просто заменить реальную таблицу *****/

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)

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

0 голосов
/ 12 октября 2016

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