Come posso aggirare SQL Server - Variazione del piano di esecuzione della funzione valore tabella in linea in base ai parametri?
-
19-08-2019 - |
Domanda
Ecco la situazione:
Ho una funzione del valore della tabella con un parametro DateTime, per non dire TDF (P_DATE), che filtra circa due milioni di righe selezionando quelle con data di colonna più piccole di P_Date e calcola alcuni valori aggregati su altre colonne.
Funziona benissimo, ma se p_date è una funzione di valore scalare personalizzata (restituendo la fine della giornata nel mio caso) il piano di esecuzione viene modificato e la query passa da 1 secondo a 1 minuto di esecuzione.
Una tabella di prova del concetto: 1.000 prodotti, 2 milioni di righe:
CREATE TABLE [dbo].[POC](
[Date] [datetime] NOT NULL,
[idProduct] [int] NOT NULL,
[Quantity] [int] NOT NULL
) ON [PRIMARY]
La funzione del valore della tabella in linea:
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 funzione valore scalare:
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
Domanda 1: funziona alla grande
SELECT * FROM [dbo].[tdf] (getdate())
Il piano di fine esecuzione:Costo aggregato flusso 13% <--- Costo scansione indice cluster 86%
Domanda 2 - Non così eccezionale
SELECT * FROM [dbo].[tdf] (dbo.EndOfDay(getdate()))
Il piano di fine esecuzione:Costo aggregato flusso 4% <--- Costo filtro 12% <--- Costo scansione indice cluster 86%
Soluzione
L'overhead è la tua funzione scalare.
Il TVF qui viene espanso come una macro in linea, quindi
SELECT * FROM [dbo].[tdf] (getdate())
diventa
SELECT idProduct, SUM(Quantity) AS TotalQuantity, max(Date) as LastDate
FROM POC
WHERE Date < getdate()
GROUP BY idProduct
Quando si utilizza la funzione scalare di fine giornata, SQL non può valutare EOD(GETDATE()) come costante.Non riesco a trovare rapidamente il mio articolo su come SQL valuta queste cose, mi dispiace.
Immagino che venga valutato per ogni riga, non in anticipo come desideri.
Calcolerei la dichiarazione EOD separatamente:
DECLARE @eod datetime;
SET @eod = dbo.EndOfDay(getdate());
SELECT * FROM [dbo].[tdf] (@eod)
Lo userei anche per la funzione EOD:
DATEADD(second, -1, DATEADD(day, 1, (DATEDIFF(day, 0, @date))))
Altri suggerimenti
Puoi riscrivere EndOfDay anche come UDF in linea e utilizzare UDF in linea nidificati. Esempi: