¿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?

StackOverflow https://stackoverflow.com/questions/450091

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%

¿Fue útil?

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

EDITAR: Otra pregunta I respondió

Otros consejos

También puede reescribir EndOfDay como UDF en línea y usar UDF en línea anidados. Ejemplos:

Muchos UDF en línea anidados son muy rápidos

Cálculo del tercer miércoles del mes con UDF en línea

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top