2012-01-22 22 views
5

En la siguiente sentencia t-sql, ¿cuántas veces se llamará a la función dbo.FUNC?Cuando hay varias llamadas al mismo UDF en una sola instrucción, ¿cuántas veces se llamará?

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

Will se llama varias veces separadas por fila, o reconoce el optimizador que está siendo utilizado varias veces en un solo estado, y sólo llamar una vez?

¿Cómo puedo probar esto? No puedo insertar en una tabla dentro de una función, por lo que el incremento de un contador no funcionará ...

Respuesta

11

Esto no se garantiza.

Debería verificar el plan de ejecución para averiguarlo. Algunos ejemplos.

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 se crea WITH SCHEMABINDING y se trata como determinista. FUNC1 no es.

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) 

Da Plan de

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 se evalúa dos veces (una vez en el filtro y una vez en un escalar compute la salida de una columna calculada utilizado tanto para la proyección y la ordenación), FUNC2 solo se evalúa una vez.

Reescribiendo como

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 

Cambia el plan de poco y ambos sólo se evalúan una vez

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

Ahora haciendo una ligera alteración de la consulta

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 

Da el opuesto del resultado original en el que FUNC2 se evalúa dos veces pero FUNC1 solo una vez.

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

¿Cómo sabe que se llamó dos veces? –

+1

El plan muestra que se llama una vez en la expresión Filtro, luego el siguiente operador es un escalar de cómputo que llama de nuevo a la función, emite el resultado como una columna 'Expr1003' y esa columna se usa tanto para la selección como para el orden . –

+1

+ buen ejemplo. Supongo que la UDF no en línea es * siempre * evaluada una vez por fila por mención a menos que haya un atajo obvio (por ejemplo, la expresión ORDER BY coincide exactamente con la expresión SELECT) – gbn

-2

sí.

el optimizador tiene los conocimientos suficientes para optimizar esto en un mismo cálculo durante la ejecución.

puede ver el plan de ejecución para verlo.

+0

Sin embargo, el plan de ejecución no parece ser preciso cuando se trata de funciones CLR, recursividad y bucles. –

+0

hace FUNc es clr enable func? –

+0

en mi caso, FUNC es un CLR UDF –

1

En primer lugar, depende de si la función es determinista.

Incluso entonces, eso solo se usará para múltiples llamadas en una sola fila.

Creo que su caso se optimizaría si la función es determinista.

+0

mi función es determinista, aunque esperaba una respuesta "estoy seguro", y no una respuesta "yo creo". –

+0

@GabrielMcAdams Cuando se trata del optimizador, no se puede garantizar nada: se le permite hacer lo que quiera siempre que conserve la semántica. Esto es particularmente problemático con las UDF escalares. En general, evite las UDF escalares llamadas a través de grandes conjuntos de datos o asegúrese de que solo se llamen una vez mediante el uso de CTE o tablas temporales para garantizar el orden lógico de las operaciones. De hecho, a veces es mucho más eficiente convertir un UDF en una tabla de búsqueda precalculada incluso sobre un dominio multi-dimansional bastante grande. –

+0

+1 Apoyando esta respuesta si creo la función 'WITH SCHEMABINDING', mueve el escalar de cálculo antes del filtro en la primera consulta de mi respuesta. –

Cuestiones relacionadas