2012-01-22 8 views
5

W następującej instrukcji t-sql, ile razy wywoływana jest funkcja dbo.FUNC?Gdy wiele wywołań do tego samego UDF znajduje się w pojedynczej instrukcji, ile razy zostanie wywołana?

SELECT 
    column1, 
    column2, 
    dbo.FUNC(column3) AS column3 
FROM table1 
WHERE dbo.FUNC(column3) >= 5 
ORDER BY dbo.FUNC(column3) DESC 

Będzie on nazywany wielu oddzielnych razy w rzędzie, czy też optymalizacji uznają, że jest używany wielokrotnie w jednym rachunku, a jedynie nazwać raz?

Jak mogę to przetestować? Nie mogę wstawić do tabeli wewnątrz funkcji, więc zwiększanie licznika nie będzie działać ...

Odpowiedz

11

To nie jest gwarantowane.

Musisz sprawdzić plan wykonania, aby się dowiedzieć. Kilka przykładów.

CREATE FUNCTION dbo.FUNC1(@p1 int) 
RETURNS int 
AS 
BEGIN 
    RETURN @p1 + 1 
END 

GO 

CREATE FUNCTION dbo.FUNC2(@p1 int) 
RETURNS int 
WITH SCHEMABINDING 
AS 
BEGIN 
    RETURN @p1 + 1 
END 

GO 
SELECT 
     OBJECTPROPERTYEX(OBJECT_ID('dbo.FUNC1'), 'IsDeterministic'), 
     OBJECTPROPERTYEX(OBJECT_ID('dbo.FUNC2'), 'IsDeterministic') 
GO 

FUNC2 powstaje WITH SCHEMABINDING i jest traktowany jako deterministyczny. FUNC1 nie jest.

SELECT 
    dbo.FUNC1(number) AS FUNC1, 
    dbo.FUNC2(number) AS FUNC2 
FROM master..spt_values 
WHERE dbo.FUNC1(number) >= 5 AND dbo.FUNC2(number) >= 5 
ORDER BY dbo.FUNC1(number), dbo.FUNC2(number) 

Daje plan

PLAN1

|--Sort(ORDER BY:([Expr1003] ASC, [Expr1004] ASC)) 
     |--Compute Scalar(DEFINE:([Expr1003]=[test].[dbo].[FUNC1]([master].[dbo].[spt_values].[number]))) 
      |--Filter(WHERE:([test].[dbo].[FUNC1]([master].[dbo].[spt_values].[number])>=(5) AND [Expr1004]>=(5))) 
       |--Compute Scalar(DEFINE:([Expr1004]=[test].[dbo].[FUNC2]([master].[dbo].[spt_values].[number]))) 
         |--Index Scan(OBJECT:([master].[dbo].[spt_values].[ix2_spt_values_nu_nc])) 

FUNC1 oceniana jest dwukrotnie (po w filtrze, a raz w skalara obliczeniowej wyprowadzania obliczoną kolumna zarówno występ oraz zamawiania) FUNC2 jest oceniany tylko raz.

Przepisywanie jako

SELECT 
    FUNC1, 
    FUNC2 
FROM master..spt_values 
CROSS APPLY (SELECT dbo.FUNC1(number), dbo.FUNC2(number)) C(FUNC1, FUNC2) 
WHERE FUNC1 >= 5 AND FUNC2 >= 5 
ORDER BY FUNC1, FUNC2 

zmianę planu lekko i oba są oceniane tylko raz

Plan 2

|--Sort(ORDER BY:([Expr1003] ASC, [Expr1004] ASC)) 
     |--Filter(WHERE:([Expr1003]>=(5))) 
      |--Compute Scalar(DEFINE:([Expr1003]=[test].[dbo].[FUNC1]([master].[dbo].[spt_values].[number]))) 
       |--Filter(WHERE:([Expr1004]>=(5))) 
         |--Compute Scalar(DEFINE:([Expr1004]=[test].[dbo].[FUNC2]([master].[dbo].[spt_values].[number]))) 
          |--Index Scan(OBJECT:([master].[dbo].[spt_values].[ix2_spt_values_nu_nc])) 

teraz co nieznaczne zmiany do zapytania

SELECT 
    FUNC1 + 10, 
    FUNC2 + 10 
FROM master..spt_values 
CROSS APPLY (SELECT dbo.FUNC1(number), dbo.FUNC2(number)) C(FUNC1, FUNC2) 
WHERE FUNC1 >= 5 AND FUNC2 >= 5 
ORDER BY FUNC1, FUNC2 

Podaje odwrotność pierwotnego wyniku, ponieważ FUNC2 jest dwukrotnie oceniany, ale FUNC1 tylko raz.

Plan 3

|--Compute Scalar(DEFINE:([Expr1005]=[Expr1003]+(10))) 
     |--Sort(ORDER BY:([Expr1003] ASC, [Expr1004] ASC)) 
      |--Filter(WHERE:([Expr1003]>=(5))) 
       |--Compute Scalar(DEFINE:([Expr1003]=[test].[dbo].[FUNC1]([master].[dbo].[spt_values].[number]))) 
         |--Filter(WHERE:([Expr1004]>=(5))) 
          |--Compute Scalar(DEFINE:([Expr1004]=[test].[dbo].[FUNC2]([master].[dbo].[spt_values].[number]), [Expr1006]=[test].[dbo].[FUNC2]([master].[dbo].[spt_values].[number])+(10))) 
           |--Index Scan(OBJECT:([master].[dbo].[spt_values].[ix2_spt_values_nu_nc])) 
+1

Skąd wiesz, że został on wywołany dwa razy? –

+1

Plan pokazuje, że jest on wywoływany raz w wyrażeniu filtru, następnie kolejnym operatorem jest skalar obliczeniowy, który ponownie wywołuje funkcję, wyświetla wynik jako kolumnę "Expr1003", a kolumna jest używana zarówno dla wyboru, jak i dla porządku przez . –

+1

+ dobry przykład. Założę się, że UDF bez tabel jest zawsze * oceniany raz na wiersz dla każdej wzmianki, chyba że istnieje oczywisty skrót (np. Wyrażenie ORDER BY dokładnie pasuje do wyrażenia SELECT) – gbn

-2

tak.

Optymalizator ma wystarczającą wiedzę, aby zoptymalizować go do tej samej wartości obliczeniowej podczas pracy.

Możesz zobaczyć plan wykonania, aby go zobaczyć.

+0

Plan wykonania wydaje się jednak nie być dokładny, jeśli chodzi o funkcje CLR, rekurencję i pętle. –

+0

czy FUNc to clr enable func? –

+0

w moim przypadku, FUNC jest CLR UDF –

1

Po pierwsze, w zależności od, czy funkcja jest deterministyczny.

Nawet wtedy będzie to używane tylko w przypadku wielu połączeń w jednym wierszu.

Wierzę, że Twój przypadek byłby zoptymalizowany, jeśli funkcja jest deterministyczna.

+0

Moja funkcja jest deterministyczna, chociaż liczyłem na odpowiedź "wiem na pewno", a nie odpowiedź "wierzę". –

+0

@GabrielMcAdams Jeśli chodzi o optymalizator, nie możesz niczego zagwarantować - może robić, co chce, o ile zachowuje semantykę. Jest to szczególnie problematyczne w przypadku skalarnych UDF. Ogólnie rzecz biorąc, unikaj skalarnych funkcji UDF nazywanych dużymi zestawami danych lub upewnij się, że są one wywoływane tylko raz, używając CTE lub tabel tymczasowych, aby zapewnić logiczną kolejność operacji. W rzeczywistości czasami skuteczniejsze jest przekształcenie UDF w wstępnie obliczoną tablicę przeglądową nawet w dość dużej domenie wielowymiarowej. –

+0

+1 Wspierając tę ​​odpowiedź, jeśli utworzę funkcję 'WITH SCHEMABINDING', to przesunie ona skalar obliczeniowy przed filtrem w pierwszym zapytaniu w mojej odpowiedzi. –

Powiązane problemy