web-dev-qa-db-fra.com

Comment configurer l'option maxrecursion pour un CTE dans une fonction valeur de table

Je suis confronté à un problème pour déclarer l'option maxrecursion pour un CTE à l'intérieur d'un TVF

voici le CTE (un simple calendrier):

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)

et la 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 ci-dessus fonctionne correctement sans l'option maxrecursion mais il existe une erreur de syntaxe avec l'option .

cordialement

34
zalath

De ce fil de discussion MSDN j’apprends que

[la] clause OPTION ne peut être utilisée qu'au niveau de l'instruction

Vous ne pouvez donc pas l'utiliser dans une expression de requête dans des définitions de vue ou des fichiers TVF intégrés, etc. Le seul moyen de l'utiliser dans votre cas est de créer le fichier TVF sans la clause OPTION et de le spécifier dans la requête utilisant le fichier TVF. Nous avons un bogue qui suit la demande autorisant l’utilisation de la clause OPTION dans toute expression de requête (par exemple, if exists() ou CTE ou view).

et plus loin

Vous ne pouvez pas modifier la valeur par défaut de cette option dans un fichier UDF. Vous devra le faire dans la déclaration faisant référence au fichier UDF.

Ainsi, dans votre exemple, vous devez spécifier la variable OPTION lorsque vous appelez votre fonction:

 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
 )

(plus tard)

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

Notez que vous ne pouvez pas contourner ce problème en ayant un deuxième fichier TVF qui ne fait que la ligne ci-dessus - vous obtenez la même erreur, si vous essayez. "[la] clause OPTION ne peut être utilisée qu'au niveau de l'instruction", et c'est final (pour l'instant).

39
AakashM

Vieux fil, je sais, mais j'avais besoin de la même chose et je l'ai simplement traitée en utilisant un fichier UDF à déclarations multiples:

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

Cela pose probablement des problèmes d'efficacité, mais je peux me le permettre dans mon cas.

22
Crisfole

Ancienne version, mais ... Je voulais juste préciser pourquoi OPTION (MAXRECURSION x) n’est pas autorisé dans une fonction de table en ligne. Cela est dû au fait que est en ligne lorsque vous les utilisez dans une requête. Et, comme nous le savons tous, vous ne pouvez pas utiliser cette option ailleurs que dans les résultats, à la toute fin de la requête. C'estLAraison pour laquelle il ne sera jamais possible de l'insérer dans un iTVF (à moins que l'analyseur et/ou l'algèbre ne fasse de la magie dans les coulisses, ce qui, à mon avis, ne le sera pas de si tôt). Les fonctions mTVF (fonctions à tables d'instructions multiples) sont différentes, car elles ne sont pas alignées (et sont si lentes qu'elles ne devraient jamais être utilisées dans les requêtes; vous pouvez toutefois les utiliser dans une affectation à une variable, mais alors encore --- méfiez-vous des boucles!).

2
darlove

Une autre façon de gérer cela consiste à diviser le problème en une paire de CTE, aucun des deux n'atteignant la limite de récursivité de 100. Le premier CTE crée une liste avec la date de début pour chaque mois de la plage. Le second CTE remplit ensuite tous les jours de chaque mois. Tant que la plage de saisie est inférieure à 100 mois, cela devrait fonctionner correctement. Si une plage de saisie supérieure à 100 mois est requise, la même idée pourrait être élargie avec un troisième CTE pour les années ajoutées avant les CTE des mois.

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
)
2
Andy B

créer un échantillon simple pour vous :)

/ * bloc crée une fonction pour tester dans sql *//* FUNCTION [fn_CTE_withLevel] (@max_level int) RETURNS TABLE COMME REVENIR
(* /

/ ******************* déclare que la table vient de remplacer la vraie 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');

/ ******************* déclare que la table vient de remplacer la vraie 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)

- ne commentez pas la fonction create /) /

0
Bishe Mtali

Une utilisation peu créative des CTE et des produits cartésiens (jointures croisées) vous permettra d’atteindre la limite MAXRECURSION de 100. de données. Si vous vous attendez à plus de différence entre @debut et @fin, vous pouvez ajuster cte3. Aussi, s'il vous plaît arrêtez de crier votre code 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')
0
mattmc3