2011-09-15 7 views
31

jestem w obliczu problemu zadeklarować opcję maxrecursion dla CTE wewnątrz TVFJak ustawić opcję maxrecursion dla CTE wewnątrz wycenione tabela funkcja

oto CTE (prosty kalendarz):

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) 

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

wyżej TVF pracuje ok bez opcji maxrecursion ale tam jest błąd składni w opcji. jakie jest rozwiązanie?

pozdrowienia

Odpowiedz

37

Od this MSDN forums thread Uczę się, że

[W] OPTION klauzula może być używany tylko na poziomie sprawozdania

Więc nie można go używać w wyrażeniu kwerendy wewnątrz widoku definicji lub wbudowane TVF itp. Jedynym sposobem użycia go w twoim przypadku jest utworzenie TVF bez klauzuli OPTION i podanie go w zapytaniu używającym TVF. Mamy błąd, który śledzi żądanie zezwalania na użycie klauzuli OPTION wewnątrz dowolnego wyrażenia zapytania (na przykład if exists() lub CTE lub widok).

i dalej

Nie można zmienić domyślną wartość tej opcji wewnątrz UDF. Musisz będzie musiał to zrobić w oświadczeniu odnoszącym się do udf.

Tak w przykładzie, należy określić OPTION kiedy połączenia czynność:

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 
) 

(później)

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

pamiętać, że nie może pracować to okrągły mając drugi TVF, który wykonuje powyższą linię - jeśli spróbujesz, otrzymasz ten sam błąd. "Klauzula o klauzuli OPTION może być używana tylko na poziomie instrukcji" i jest ostateczna (na razie).

22

Stary wątek, wiem, ale musiałem to samo i po prostu do czynienia z nim za pomocą UDF multi-statement:

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 

prawdopodobnie istnieją problemy wydajności z tym, ale mogę sobie na to pozwolić w mój przypadek.

+3

Ta odpowiedź może być trochę lepiej, ponieważ nie ma potrzeby, aby pamiętać o CTE (bez limitu rekurencji) w funkcji. –

+0

Oczywiście rozwiązuje problem, ale ma "prawdopodobnie wadę", ponieważ ta funkcja z wartościami przechowywanymi w tabeli jest "niewypowiedziana". Może być krwisty pod względem wydajności. – pkuderov

+0

Jak powiedziałem: "Prawdopodobnie istnieją problemy z wydajnością, ale mogę sobie na to pozwolić w moim przypadku". – Crisfole

2

Innym sposobem radzenia sobie z tym problemem jest podzielenie problemu na parę CTE, z których żaden nie przekracza limitu rekursji równego 100. Pierwszy CTE tworzy listę z datą rozpoczęcia każdego miesiąca w zakresie. Drugi CTE następnie wypełnia wszystkie dni każdego miesiąca. Dopóki zakres wejściowy wynosi mniej niż 100 miesięcy, powinien działać poprawnie. Jeśli wymagany jest zakres wejściowy dłuższy niż 100 miesięcy, ten sam pomysł mógłby zostać rozszerzony o trzeci współczynnik CTE na lata dodany przed miesiącem 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

Stary problem, ale ... Chciałem tylko wyjaśnić, dlaczego OPCJA (MAXRECURSION x) nie jest dozwolone w funkcji wycenione tabela in-line. Wynika to z faktu, że iTVF pod numerem otrzymuje informację o numerze, gdy używa się ich w zapytaniu. I, jak wszyscy wiemy, nie możesz umieścić tej opcji nigdzie indziej, z wyjątkiem samego końca zapytania. To jest THE, ponieważ nigdy nie będzie możliwe umieszczenie go wewnątrz iTVF (chyba że parser i/lub algebrizer wykona jakąś magię za kulisami, co nie wydaje mi się, że wkrótce to nastąpi). mTVF (funkcje wielowektorowe o wartościach tabelarycznych) to inna historia, ponieważ nie są one wbudowane (i są tak powolne, że nigdy nie powinny być używane w zapytaniach; można jednak użyć ich w przypisaniu do zmiennej, ale następnie ponownie --- uważaj na pętle!).

0

Nieco kreatywne użycie CTE i produktów kartezjańskich (skrzyżowanie krzyżowe) sprawi, że osiągniesz limit MAXRECURSION wynoszący 100. 3 CTE z limitem 4 rekordów na ostatnim z 40 000 rekordów, co będzie dobre na więcej ponad 100 lat danych. Jeśli oczekujesz większej różnicy między @debut i @finem, możesz dostosować cte3. Przestań też WYKLUCZAĆ 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') 
Powiązane problemy