Как я могу обойти SQL Server - изменение плана выполнения функции встроенного табличного значения в зависимости от параметров?
-
19-08-2019 - |
Вопрос
Вот такая ситуация:
У меня есть функция табличного значения с параметром datetime , чтобы не сказать tdf(p_date) ,
это фильтрует около двух миллионов строк, выбирая те, у которых дата столбца меньше, чем p_date, и вычисляет некоторые совокупные значения для других столбцов.
Это отлично работает, но если p_date является пользовательской функцией скалярного значения (в моем случае возвращающей конец дня), план выполнения изменяется, и время выполнения запроса увеличивается с 1 секунды до 1 минуты.
Таблица подтверждения концепции - 1 тыс. продуктов, 2 млн строк:
CREATE TABLE [dbo].[POC](
[Date] [datetime] NOT NULL,
[idProduct] [int] NOT NULL,
[Quantity] [int] NOT NULL
) ON [PRIMARY]
Функция встроенного табличного значения:
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
)
Функция скалярного значения:
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
Запрос 1 - Работает отлично
SELECT * FROM [dbo].[tdf] (getdate())
Завершение выполнения плана:Совокупная стоимость потока 13% <--- Стоимость сканирования кластеризованного индекса 86%
Запрос 2 - Не так уж и велик
SELECT * FROM [dbo].[tdf] (dbo.EndOfDay(getdate()))
Завершение выполнения плана:Совокупная стоимость потока 4% <--- Стоимость фильтра 12% <--- Стоимость сканирования кластеризованного индекса 86%
Решение
Накладные расходы - это ваша скалярная функция.
TVF здесь расширяется как встроенный макрос, так что
SELECT * FROM [dbo].[tdf] (getdate())
становится
SELECT idProduct, SUM(Quantity) AS TotalQuantity, max(Date) as LastDate
FROM POC
WHERE Date < getdate()
GROUP BY idProduct
Когда вы используете скалярную функцию конца дня, SQL не может оценить EOD(GETDATE()) как константу.Извините, я не могу быстро найти свою статью о том, как SQL оценивает этот материал.
Я предполагаю, что он оценивается для каждой строки, а не заранее, как вы хотите.
Я бы сформулировал заявление EOD отдельно:
DECLARE @eod datetime;
SET @eod = dbo.EndOfDay(getdate());
SELECT * FROM [dbo].[tdf] (@eod)
Я бы также использовал это для функции EOD:
DATEADD(second, -1, DATEADD(day, 1, (DATEDIFF(day, 0, @date))))
Другие советы
Вы также можете переписать EndOfDay как встроенный UDF и использовать вложенные встроенные UDF. Примеры: р>
Многие вложенные встроенные функции очень быстры
Расчет третьей среды месяца с помощью встроенных пользовательских функций