Una función con valores de tabla de varias instrucciones devuelve su resultado en una variable de tabla.
¿Se reutilizan estos resultados alguna vez, o la función siempre se evalúa completamente cada vez que se llama?
Una función con valores de tabla de varias instrucciones devuelve su resultado en una variable de tabla.
¿Se reutilizan estos resultados alguna vez, o la función siempre se evalúa completamente cada vez que se llama?
Respuestas:
Los resultados de una función con valores de tabla de varias instrucciones (msTVF) nunca se almacenan en caché o se reutilizan en las instrucciones (o conexiones), pero hay un par de formas en que un resultado de msTVF se puede reutilizar dentro de la misma instrucción. En ese sentido, un msTVF no se repobla necesariamente cada vez que se llama.
Este (deliberadamente ineficiente) msTVF devuelve un rango específico de enteros, con una marca de tiempo en cada fila:
IF OBJECT_ID(N'dbo.IntegerRange', 'TF') IS NOT NULL
DROP FUNCTION dbo.IntegerRange;
GO
CREATE FUNCTION dbo.IntegerRange (@From integer, @To integer)
RETURNS @T table
(
n integer PRIMARY KEY,
ts datetime DEFAULT CURRENT_TIMESTAMP
)
WITH SCHEMABINDING
AS
BEGIN
WHILE @From <= @To
BEGIN
INSERT @T (n)
VALUES (@From);
SET @From = @From + 1;
END;
RETURN;
END;
Si todos los parámetros para la llamada a la función son constantes (o constantes de tiempo de ejecución), el plan de ejecución rellenará el resultado de la variable de tabla una vez. El resto del plan puede acceder a la variable de tabla muchas veces. La naturaleza estática de la variable de tabla puede reconocerse a partir del plan de ejecución. Por ejemplo:
SELECT
IR.n,
IR.ts
FROM dbo.IntegerRange(1, 5) AS IR
ORDER BY
IR.n;
Devuelve un resultado similar a:
El plan de ejecución es:
El operador de secuencia primero llama al operador de función de valor de tabla, que llena la variable de tabla (tenga en cuenta que este operador no devuelve filas). A continuación, la secuencia llama a su segunda entrada, que devuelve el contenido de la variable de la tabla (en este caso, utilizando un análisis de índice agrupado).
El obsequio de que el plan está utilizando un resultado de variable de tabla 'estática' es el operador de Función de valor de tabla debajo de una secuencia: la variable de tabla debe completarse una vez antes de que el resto del plan pueda ponerse en marcha.
Para mostrar el resultado de la variable de tabla al que se accede más de una vez, utilizaremos una segunda tabla con filas numeradas del 1 al 5:
IF OBJECT_ID(N'dbo.T', 'U') IS NOT NULL
DROP TABLE dbo.T;
CREATE TABLE dbo.T (i integer NOT NULL);
INSERT dbo.T (i)
VALUES (1), (2), (3), (4), (5);
Y una nueva consulta que une esta tabla a nuestra función (esto también podría escribirse como APPLY
):
SELECT T.i,
IR.n,
IR.ts
FROM dbo.T AS T
JOIN dbo.IntegerRange(1, 5) AS IR
ON IR.n = T.i;
El resultado es:
El plan de ejecución:
Como antes, la secuencia llena primero el resultado de msTVF de la variable de tabla. A continuación, los bucles anidados se utilizan para unir cada fila de la tabla T
a una fila del resultado de msTVF. Como la definición de la función incluía un índice útil en la variable de la tabla, se puede utilizar una búsqueda de índice.
El punto clave es que cuando los parámetros para msTVF son constantes (incluidas variables y parámetros) o se tratan como constantes de tiempo de ejecución para la declaración del motor de ejecución, el plan contará con dos operadores separados para el resultado de la variable de tabla msTVF: uno para completar el mesa; otro para acceder a los resultados, posiblemente accediendo a la tabla varias veces, y posiblemente haciendo uso de índices declarados en la definición de la función.
Para resaltar las diferencias cuando se utilizan parámetros correlacionados (referencias externas) o parámetros de función no constantes, cambiaremos el contenido de la tabla T
para que la función tenga mucho más trabajo por hacer:
TRUNCATE TABLE dbo.T;
INSERT dbo.T (i)
VALUES (50001), (50002), (50003), (50004), (50005);
La siguiente consulta modificada ahora usa una referencia externa a la tabla T
en uno de los parámetros de función:
SELECT T.i,
IR.n,
IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;
Esta consulta demora alrededor de 8 segundos para devolver resultados como:
Observe la diferencia horaria entre filas en la columna ts
. La WHERE
cláusula limita el resultado final para una salida de tamaño razonable, pero la función ineficiente todavía tarda un tiempo en llenar la variable de la tabla con 50,000 filas impares (dependiendo del valor correlacionado de la i
tabla T
).
El plan de ejecución es:
Observe la falta de un operador de secuencia. Ahora, hay un único operador de Función de valor de tabla que llena la variable de tabla y devuelve sus filas en cada iteración de la unión de bucles anidados.
Para ser claros: con solo 5 filas en la tabla T, el operador de Función de valor de tabla se ejecuta 5 veces. Genera 50.001 filas en la primera iteración, 50.002 en la segunda ... y así sucesivamente. La variable de la tabla se 'desecha' (trunca) entre iteraciones, por lo que cada una de las cinco llamadas es una población completa. Es por eso que es tan lento y cada fila tarda aproximadamente el mismo tiempo en aparecer en el resultado.
Notas al margen:
Naturalmente, el escenario anterior está ideado deliberadamente para mostrar cuán pobre puede ser el rendimiento cuando el msTVF llena muchas filas en cada iteración.
Una implementación sensata del código anterior establecería ambos parámetros de msTVF i
y eliminaría la WHERE
cláusula redundante . La variable de tabla todavía se truncaría y se volvería a llenar en cada iteración, pero solo con una fila cada vez.
También podríamos obtener los i
valores mínimos y máximos T
y almacenarlos en variables en un paso anterior. Llamar a la función con variables en lugar de parámetros correlacionados permitiría usar el patrón variable de tabla 'estático' como se indicó anteriormente.
Volviendo a abordar la pregunta original una vez más, donde el patrón estático de Secuencia no se puede usar, SQL Server puede evitar truncar y repoblar la variable de tabla msTVF si ninguno de los parámetros correlacionados ha cambiado desde la iteración previa de una unión de bucle anidado.
Para demostrar esto, reemplazaremos el contenido de T
con cinco valores idénticos i
:
TRUNCATE TABLE dbo.T;
INSERT dbo.T (i)
VALUES (50005), (50005), (50005), (50005), (50005);
La consulta con un parámetro correlacionado nuevamente:
SELECT T.i,
IR.n,
IR.ts
FROM dbo.T AS T
CROSS APPLY dbo.IntegerRange(1, T.i) AS IR
WHERE IR.n = T.i;
Esta vez, los resultados aparecen en alrededor de 1,5 segundos :
Tenga en cuenta las marcas de tiempo idénticas en cada fila. El resultado almacenado en caché en la variable de tabla se reutiliza para las iteraciones posteriores en las que el valor correlacionado i
no cambia. Reutilizar el resultado es mucho más rápido que insertar 50,005 filas cada vez.
El plan de ejecución es muy similar al anterior:
La diferencia clave está en las propiedades Rebobinados reales y Rebobinados reales del operador Función de valor de tabla:
Cuando los parámetros correlacionados no cambian, SQL Server puede reproducir (rebobinar) los resultados actuales en la variable de tabla. Cuando la correlación cambia, SQL Server debe truncar y repoblar la variable de la tabla (volver a vincular). El nuevo enlace ocurre en la primera iteración; Las cuatro iteraciones posteriores son todas rebobinadas ya que el valor de no T.i
ha cambiado.