web-dev-qa-db-ja.com

Table-Valued-Function内でCTEのmaxrecursionオプションを設定する方法

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オプションなしで正常に実行されていますが、オプションに構文エラーがあります。解決策は何ですか?

36
zalath

このMSDNフォーラムスレッド から学ぶ

[the] OPTION句はステートメントレベルでのみ使用できます

したがって、ビュー定義またはインラインTVFなどのクエリ式内で使用することはできません。あなたのケースで使用する唯一の方法は、OPTION句なしでTVFを作成し、TVFを使用するクエリで指定することです。クエリ式内でOPTION句の使用を許可するリクエストを追跡するバグがあります(たとえば、if exists()またはCTEまたはビュー)。

そしてさらに

Udf内でそのオプションのデフォルト値を変更することはできません。 udfを参照するステートメントで行う必要があります。

したがって、この例では、関数をcallするときに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 )

上記の行を実行するだけの2番目のTVFを使用することで、これを回避することはできません。同じエラーが発生すると、同じエラーが発生します。 「[the] OPTION句はステートメントレベルでのみ使用できます」、そしてこれが最後です(今のところ)。

41
AakashM

古いスレッド、私は知っていますが、私は同じことを必要としており、マルチステートメント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

これにはおそらく効率の問題がありますが、私の場合は余裕があります。

22
Crisfole

古い問題ですが...インラインテーブル値関数でOPTION(MAXRECURSION x)が許可されない理由を明確にしたかっただけです。これは、クエリでiTVFを使用する場合、iTVFのインライン化であるためです。そして、私たち全員が知っているように、このオプションをクエリの最後に保存する他の場所に置くことはできません。これは[〜#〜] the [〜#〜] iTVF内に配置できない理由です(パーサーや代数器が舞台裏で魔法をかけない限り、いつでもそうなるとは思わない)。 mTVF(複数ステートメントのテーブル値関数)はインライン化されないため(また、クエリで使用されるべきではないほど遅いため、変数への代入で使用しても構いませんが)、別の話です。そして再び---ループに注意してください!)。

2
darlove

これを処理するもう1つの方法は、問題を1組のCTEに分割することです。どちらも100の再帰制限に達しません。最初のCTEは、範囲内の各月の開始日を含むリストを作成します。次に、2番目のCTEが各月のすべての日を埋めます。入力範囲が100か月未満である限り、正常に機能するはずです。 100か月を超える入力範囲が必要な場合、CTEの数か月前に3年目の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
)
2
Andy B

簡単なサンプルを作成してください:)

/ * sqlのテスト用のブロック作成関数*// * FUNCTION [fn_CTE_withLevel](@max_level int)RETURNS TABLE AS 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
Bishe Mtali

CTEとデカルト積(クロスジョイン)を少し創造的に使用すると、100のMAXRECURSION制限を回避することができます。 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')
0
mattmc3