Como posso contornar SQL Server - inline Tabela Valor variação plano de execução de função com base em parâmetros?
-
19-08-2019 - |
Pergunta
Aqui está a situação:
Eu tenho uma função de valor de tabela com um parâmetro de data e hora, para não dizer TDF (p_date),
que os filtros de cerca de dois milhões de linhas selecionando aqueles com data de coluna menor do que p_date e calcula alguns valores agregados sobre outras colunas.
Ele funciona muito bem, mas se p_date é uma função valor escalar personalizado (retornando no final do dia no meu caso) o plano de execução é alterado um a consulta vai de 1 seg a 1 minuto de tempo de execução.
A prova da tabela conceito - 1K produtos, linhas 2M:
CREATE TABLE [dbo].[POC](
[Date] [datetime] NOT NULL,
[idProduct] [int] NOT NULL,
[Quantity] [int] NOT NULL
) ON [PRIMARY]
A função de valor de tabela em linha:
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
)
A função 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 - grande de Trabalho
SELECT * FROM [dbo].[tdf] (getdate())
O fim do plano de execução:
Custo fluxo agregado de 13% Consulta 2 - Não é tão grande O fim do plano de execução:
Custo fluxo agregado de 4% <--- Custo Filtro 12% SELECT * FROM [dbo].[tdf] (dbo.EndOfDay(getdate()))
Solução
A sobrecarga é a sua função escalar.
O TVF aqui é expandido como uma macro in-line de modo
SELECT * FROM [dbo].[tdf] (getdate())
se torna
SELECT idProduct, SUM(Quantity) AS TotalQuantity, max(Date) as LastDate
FROM POC
WHERE Date < getdate()
GROUP BY idProduct
Quando você usa final de função dia escalar, SQL não pode avaliar a EOD (GETDATE ()) como uma constante. Não consigo encontrar meu artigo rapidamente sobre como SQL avalia este material, desculpe.
Eu acho que ele está sendo avaliada para cada linha, e não adiantado como você quer.
Eu Calulate a declaração EOD separadamente:
DECLARE @eod datetime;
SET @eod = dbo.EndOfDay(getdate());
SELECT * FROM [dbo].[tdf] (@eod)
Eu também usar este para a função de EOD:
DATEADD(second, -1, DATEADD(day, 1, (DATEDIFF(day, 0, @date))))
Outras dicas
Você pode reescrever EndOfDay como um inline UDF também, e usar UDFs em linha aninhados. Exemplos: