Улучшить SQL-запрос, используя функции - PullRequest
3 голосов
/ 25 сентября 2011

Я пытаюсь улучшить читаемость запроса с помощью функции в SQL Server Express 2008. Вот пример того, что я пытаюсь сделать.

У меня есть таблица, в которой мы храним показания максимальной температурыв час дня, затем я хочу выбрать все дни, когда максимальная температура между 8-10 утра была выше максимальной температуры между 12-2 вечера

Так вот как это выглядит:

DECLARE @TableA TABLE ([Date] DATE, [Time] TIME(0), HighTemp DECIMAL(6,2)); 

INSERT @TableA VALUES 
('2011-09-10','08:00:00',38.15), 
('2011-09-10','09:00:00',38.32), 
('2011-09-10','10:00:00',38.17), 
('2011-09-10','11:00:00',38.10), 
('2011-09-10','12:00:00',38.05), 
('2011-09-10','13:00:00',38.15), 
('2011-09-10','14:00:00',38.30), 

('2011-09-11','08:00:00',38.12), 
('2011-09-11','09:00:00',38.09), 
('2011-09-11','10:00:00',38.07), 
('2011-09-11','11:00:00',38.15), 
('2011-09-11','12:00:00',38.13), 
('2011-09-11','13:00:00',38.11), 
('2011-09-11','14:00:00',38.10), 

('2011-09-12','08:00:00',38.30), 
('2011-09-12','09:00:00',38.33), 
('2011-09-12','10:00:00',38.35), 
('2011-09-12','11:00:00',38.30), 
('2011-09-12','12:00:00',38.25), 
('2011-09-12','13:00:00',38.23), 
('2011-09-12','14:00:00',38.20)

select distinct [DATE] from @TableA maintbl
where 
-- Select the high temp between 08:00:00-10:00:00
(select MAX(HighTemp) from @TableA tmptbl where tmptbl.Time >= '08:00:00' and tmptbl.Time <= '10:00:00' and maintbl.Date = tmptbl.Date)
>
-- Select the high between 12:00:00-14:00:00
(select MAX(HighTemp) from @TableA tmptbl where tmptbl.Time >= '12:00:00' and tmptbl.Time <= '14:00:00' and maintbl.Date = tmptbl.Date)

Запрос выполняется хорошо (быстро), и результат для вышеупомянутого запроса должен быть следующим: 2011-09-10 2011-09-12

Теперь я попытался упростить запрос с помощью функции, котораяизвлекает максимальное значение tempreture за определенный день и период времени, поэтому запрос легче читать, например:

select distinct [DATE] from @TableA maintbl
where GetPeriodHigh(maintbl.Date, '08:00:00', '10:00:00') > GetPeriodHigh(maintbl.Date, '12:00:00', '14:00:00')

А функция выглядит так:

CREATE FUNCTION [dbo].[GetPeriodHigh] 
(
    @Date date,
    @From time,
    @To time
)
RETURNS decimal(6,2)
AS
BEGIN

    declare @res decimal(6,2)

    select @res = MAX(high) from MyTable
    where Time >= @from and Time <= @to and Date = @Date

    return @res
END

Проблемав том, что выполнение запроса с использованием функции занимает LOOONG времени, на самом деле я никогда не видел, чтобы он заканчивался, похоже, что он находится в каком-то бесконечном цикле ...

Любые идеи, почему это так, и я могу сделать всесделать, чтобы упростить мой запрос?

Thx.

Ответы [ 2 ]

5 голосов
/ 25 сентября 2011

Скалярные функции, которые осуществляют доступ к данным, обычно отстой и их лучше избегать. Оптимизатор не раскрывает их, и это в основном заставляет запрос функции быть внутренней стороной объединения вложенных циклов независимо от их пригодности.

Что еще хуже, у вас может не быть правильной индексации для оценки предиката Time >= @from and Time <= @to and Date = @Date внутри функции, что означает, что для каждой строки во внешнем запросе вы выполняете 2 сканирования таблицы с помощью вызовов функций.

Это отсутствие индексов также имеет место в вашем исходном примере, и с помощью встроенной версии видно, что оптимизатор запросов может эффективно переписать это как два MAX / GROUP BY запросов с различными предложениями WHERE затем объединить объединить результаты вместе. Когда логика находится в скалярных UDF, этот вид преобразования в настоящее время не рассматривается.

Plan

Другой подход, который вы можете попробовать, это

SELECT [Date]
FROM @TableA
WHERE Time BETWEEN '08:00:00' AND '10:00:00' 
      OR Time BETWEEN '12:00:00' AND '14:00:00'
GROUP BY [Date]
HAVING MAX(CASE 
               WHEN Time BETWEEN '08:00:00' AND '10:00:00' THEN HighTemp END) > 
       MAX(CASE 
               WHEN Time BETWEEN '12:00:00' AND '14:00:00' THEN HighTemp END)
2 голосов
/ 26 сентября 2011

Чтобы повысить производительность, попробуйте переписать UDF со скалярным значением как UDF со встроенной таблицей.

Некоторые ссылки:

http://sqlblog.com/blogs/alexander_kuznetsov/archive/2008/05/23/reuse-your-code-with-cross-apply.aspx

http://sqlblog.com/blogs/alexander_kuznetsov/archive/2008/04/21/not-all-udfs-are-bad-for-performance.aspx

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...