¿Cómo puedo evitar SQL Server: variación del plan de ejecución de la función del valor de la tabla en línea según los parámetros?
-
19-08-2019 - |
Pregunta
Aquí está la situación:
Tengo una función de valor de tabla con un parámetro de fecha y hora, no digamos tdf (p_date),
que filtra aproximadamente dos millones de filas seleccionando aquellas con una fecha de columna menor que p_date y calcula algunos valores agregados en otras columnas.
Funciona muy bien, pero si p_date es una función de valor escalar personalizada (devolviendo el final del día en mi caso) el plan de ejecución se modifica y la consulta pasa de 1 segundo a 1 minuto de tiempo de ejecución.
Una tabla de prueba de concepto: 1K productos, 2M filas:
CREATE TABLE [dbo].[POC](
[Date] [datetime] NOT NULL,
[idProduct] [int] NOT NULL,
[Quantity] [int] NOT NULL
) ON [PRIMARY]
La función de valor de la tabla en línea:
CREATE FUNCTION tdf (@p_date datetime)
RETURNS TABLE
AS
RETURN
(
SELECT idProduct, SUM(Quantity) AS TotalQuantity,
max(Date) as LastDate
FROM POC
WHERE (Date < @p_date)
GROUP BY idProduct
)
La función de valor escalar:
CREATE FUNCTION [dbo].[EndOfDay] (@date datetime)
RETURNS datetime
AS
BEGIN
DECLARE @res datetime
SET @res=dateadd(second, -1,
dateadd(day, 1,
dateadd(ms, -datepart(ms, @date),
dateadd(ss, -datepart(ss, @date),
dateadd(mi,- datepart(mi,@date),
dateadd(hh, -datepart(hh, @date), @date))))))
RETURN @res
END
Consulta 1 - Funciona muy bien
SELECT * FROM [dbo].[tdf] (getdate())
El final del plan de ejecución: Costo agregado de flujo 13% & Lt; --- Costo de escaneo de índice agrupado 86%
Consulta 2 - No tan genial
SELECT * FROM [dbo].[tdf] (dbo.EndOfDay(getdate()))
El final del plan de ejecución: Costo agregado de flujo 4% & Lt; --- Costo de filtro 12% & Lt; --- Costo de escaneo de índice agrupado 86%
Solución
La sobrecarga es su función escalar.
El TVF aquí se expande como una macro en línea, así que
SELECT * FROM [dbo].[tdf] (getdate())
se convierte
SELECT idProduct, SUM(Quantity) AS TotalQuantity, max(Date) as LastDate
FROM POC
WHERE Date < getdate()
GROUP BY idProduct
Cuando usa la función escalar de fin de día, SQL no puede evaluar el EOD (GETDATE ()) como una constante. No puedo encontrar mi artículo rápidamente sobre cómo SQL evalúa estas cosas, lo siento.
Supongo que se está evaluando para cada fila, no por adelantado como quieras.
Calculo la declaración EOD por separado:
DECLARE @eod datetime;
SET @eod = dbo.EndOfDay(getdate());
SELECT * FROM [dbo].[tdf] (@eod)
También usaría esto para la función EOD:
DATEADD(second, -1, DATEADD(day, 1, (DATEDIFF(day, 0, @date))))
Otros consejos
También puede reescribir EndOfDay como UDF en línea y usar UDF en línea anidados. Ejemplos: