Проблемы производительности TSQL при использовании DATEADD в предложении where - PullRequest
0 голосов
/ 19 сентября 2011

У меня есть запрос с использованием метода DATEADD, который занимает много времени.Я постараюсь упростить то, что мы делаем.Мы следим за темпрерами, и каждые 5 минут мы храним самые высокие и самые низкие температуры в таблице A

Дата |Время |MaxTemp |MinTemp
2011-09-18 |12:05:00 |38,15 |38,099
2011-09-18 |12:10:00 |38,20 |38.10
2011-09-18 |12:15:00 |38.22 |38,17
2011-09-18 |12:20:00 |38.21 |38,20
...
2011-09-19 |11:50:00 |38.17 |38.10
2011-09-19 |12:55:00 |38.32 |38,27
2011-09-19 |12:00:00 |38.30 |38,20

Столбцы даты / времени имеют тип дата / время (а не дата-время)

В другой таблице (таблица B) мы храним некоторые данные за весь день, где деньс полудня (12:00) до полудня (не с полуночи до полуночи).

Таким образом, столбцы таблицы B включают в себя: Дата (дата только без времени)
ShiftManager
MaxTemp (это максимальная температура длявсе 24 часа, начиная с полудня этой даты до полудня следующего дня)
MinTemp

Я получаю таблицу B со всеми данными, и мне просто нужно обновить MaxTemp и MinTemp, используя таблицу A
Дляпример: для 18.09.2011 мне нужно максимальное временное показание, которое было между 18.09.2011, 12:00 и 19.09.2011, 12:00.
. В приведенном выше примере с таблицей A результат повторного платежа будет равен 38,32.это MAX (MaxTemp) на требуемый период.

Используемый мной SQL:

update TableB
set MaxTemp = (
select MAX(HighTemp) from TableA
where
(Date=TableB.Date and Time > '12:00:00') 
or 
(Date=DATEADD(dd,1,TableB.Date) and Time <= '12:00:00')
)

И это занимает много времени (если я удаляю метод DATEADD, это быстро).

Вот упрощенный пример, который показывает мои данные и ожидаемоерезультат:

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

INSERT @TableA VALUES
('2011-09-18','12:05:00',38.15),
('2011-09-18','12:10:00',38.20),
('2011-09-18','12:15:00',38.22),
('2011-09-19','11:50:00',38.17),
('2011-09-19','11:55:00',38.32),
('2011-09-19','12:00:00',38.31),
('2011-09-19','12:05:00',38.33),
('2011-09-19','12:10:00',38.40),
('2011-09-19','12:15:00',38.12),
('2011-09-20','11:50:00',38.27),
('2011-09-20','11:55:00',38.42),
('2011-09-20','12:00:00',38.16);

INSERT @TableB VALUES
('2011-09-18', 0),
('2011-09-19', 0);

-- This is how I get the data, now I just need to update the max temp for each day

with TableB(d, maxt) as
(
select * from @TableB
)
update TableB
set maxt = ( 
select MAX(HighTemp) from @TableA 
where
(Date=TableB.d and Time > '12:00:00')
or
(Date=DATEADD(dd,1,TableB.d) and Time <= '12:00:00')
)

select * from @TableB

Надеюсь, я смог сам себя объяснить, есть идеи, как мне сделать это по-другому?Thx!

Ответы [ 3 ]

3 голосов
/ 19 сентября 2011

Функции в столбце обычно снижают производительность.Так может ИЛИ.

Однако я предполагаю, что вы хотите, а не ИЛИ, потому что это диапазон.

Итак, применяя некоторую логику и имея только одно вычисление

update TableB
 set MaxTemp =
 (
    select MAX(HighTemp) from TableA
    where
    (Date + Time - 0.5 = TableB.Date)
 )

(Date + Time - 0.5) изменится с полудня на полдень с полуночи до полуночи (0,5 = 12 часов).Что еще более важно, вы можете сделать этот вычисляемый столбец и проиндексировать его

Точнее, Date + Time - 0.5 - это DATEADD(hour, -12, Date+Time), предполагая, что Date и Time являются действительными датами / временем, а неvarchar ...

Редактировать: этот ответ неправильный , но я оставлю это как "что не делать"

Смотрите это больше:

1 голос
/ 20 сентября 2011

Вероятно, было бы намного проще, если бы вы использовали один столбец SMALLDATETIME вместо разделения этих данных на столбцы DATE / TIME.Также я предполагаю, что вы используете SQL Server 2008, а не предыдущую версию, где вы храните DATE / TIME данные в виде строк.Укажите версию SQL Server и используемые типы данных.

DECLARE @d TABLE ([Date] DATE, [Time] TIME(0), MaxTemp DECIMAL(6,3), MinTemp DECIMAL(6,3));

INSERT @d VALUES
('2011-09-18','12:05:00',38.15,38.099),
('2011-09-18','12:10:00',38.20,38.10),
('2011-09-18','12:15:00',38.22,38.17),
('2011-09-18','12:20:00',38.21,38.20),
('2011-09-19','11:50:00',38.17,38.10),
('2011-09-19','12:55:00',38.32,38.27),
('2011-09-19','12:00:00',38.30,38.20);

SELECT '-- before update';
SELECT * FROM @d;

;WITH d(d,t,dtr,maxt) AS
(
    SELECT [Date], [Time], DATEADD(HOUR, -12, CONVERT(SMALLDATETIME, CONVERT(CHAR(8), 
        [Date], 112) + ' ' + CONVERT(CHAR(8), [Time], 108))), MaxTemp FROM @d 
),
d2(dtr, maxt) AS 
(
    SELECT CONVERT([Date], dtr), MAX(maxt) FROM d
    GROUP BY CONVERT([Date], dtr)
)
UPDATE d SET maxt = d2.maxt FROM d
    INNER JOIN d2 ON d.dtr >= d2.dtr AND d.dtr < DATEADD(DAY, 1, d2.dtr);

SELECT '-- after update';
SELECT * FROM @d;

Результаты:

-- before update

2011-09-18  12:05:00    38.150  38.099
2011-09-18  12:10:00    38.200  38.100
2011-09-18  12:15:00    38.220  38.170
2011-09-18  12:20:00    38.210  38.200
2011-09-19  11:50:00    38.170  38.100
2011-09-19  12:55:00    38.320  38.270
2011-09-19  12:00:00    38.300  38.200

-- after update

2011-09-18  12:05:00    38.220  38.099
2011-09-18  12:10:00    38.220  38.100
2011-09-18  12:15:00    38.220  38.170
2011-09-18  12:20:00    38.220  38.200
2011-09-19  11:50:00    38.220  38.100
2011-09-19  12:55:00    38.320  38.270
2011-09-19  12:00:00    38.320  38.200

Предположительно, вы также хотите обновить MinTemp, и это просто:

;WITH d(d,t,dtr,maxt,mint) AS
(
    SELECT [Date], [Time], DATEADD(HOUR, -12,
         CONVERT(SMALLDATETIME, CONVERT(CHAR(8), [Date], 112) 
         + ' ' + CONVERT(CHAR(8), [Time], 108))), MaxTemp, MaxTemp
    FROM @d 
),
d2(dtr, maxt, mint) AS 
(
    SELECT CONVERT([Date], dtr), MAX(maxt), MIN(mint) FROM d
    GROUP BY CONVERT([Date], dtr)
)
UPDATE d
    SET maxt = d2.maxt, mint = d2.maxt
    FROM d
    INNER JOIN d2
        ON d.dtr >= d2.dtr
        AND d.dtr < DATEADD(DAY, 1, d2.dtr);

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

USE [tempdb];
GO

CREATE TABLE dbo.d
(
    [Date] SMALLDATETIME, 
    MaxTemp DECIMAL(6,3), 
    MinTemp DECIMAL(6,3),
    RoundedDate AS (CONVERT(DATE, DATEADD(HOUR, -12, [Date]))) PERSISTED
);

CREATE INDEX rd ON dbo.d(RoundedDate);

INSERT dbo.d([Date],MaxTemp,MinTemp) VALUES
('2011-09-18 12:05:00',38.15,38.099),
('2011-09-18 12:10:00',38.20,38.10),
('2011-09-18 12:15:00',38.22,38.17),
('2011-09-18 12:20:00',38.21,38.20),
('2011-09-19 11:50:00',38.17,38.10),
('2011-09-19 12:55:00',38.32,38.27),
('2011-09-19 12:00:00',38.30,38.20);

Тогда ваше обновление настолько простое, а план гораздо приятнее:

;WITH g(RoundedDate,MaxTemp)
AS
(
    SELECT RoundedDate, MAX(MaxTemp)
        FROM dbo.d
        GROUP BY RoundedDate
)
UPDATE d
    SET MaxTemp = g.MaxTemp
    FROM dbo.d AS d
    INNER JOIN g
    ON d.RoundedDate = g.RoundedDate;

Наконец, одна из причин, по которой ваш существующий запрос -вероятно, так долго, что вы обновляете все время, каждый раз.Изменяются ли данные с прошлой недели?Возможно нет.Так почему бы не ограничить предложение WHERE только последними данными?Я не вижу необходимости пересматривать что-либо раньше, чем вчера, если вы постоянно не получаете пересмотренные оценки того, насколько тепло было в прошлый вторник в полдень.Так почему же в вашем текущем запросе нет предложений WHERE, ограничивающих диапазон дат, в котором он пытается выполнить эту работу?Вы действительно хотите обновить все возможности, КАЖДЫЙ раз?Это, вероятно, то, что вы должны делать только один раз в день, иногда днем, чтобы обновить вчера.Поэтому неважно, займет ли это 2 секунды или 2,5 секунды.

0 голосов
/ 20 сентября 2011

Вам может понадобиться использовать -12 в зависимости от даты в качестве даты начала или окончания для внутреннего времени с полудня до полудня.

    update tableA 
    set tableAx.MaxTemp = MAX(TableB.HighTemp)
    from tableA as tableAx
    join TableB 
    on tableAx.Date = CAST(DATEADD(hh,12,TableB.[Date]+TableB.[Time]) as Date) 
    group by tableAx.Date 

Из-за 12-часового смещения не уверен, сколько выиграет, если поместить TableB Date плюс Time непосредственно в поле DateTime. Невозможно выйти из DATEADD, и выходные данные из функции не индексируются, даже если параметры, входящие в функцию, индексируются. То, что вы могли бы сделать, это создать вычисляемый столбец, который = дата + время +/- 12h и индексировать этот столбец.

Как и рекомендация от Arron, обновлять только те, у которых нет значений.

    update tableA 
    set tableAx.MaxTemp = MAX(TableB.HighTemp)
    from tableA as tableAx
    join TableB 
    on tableAx.Date = CAST(DATEADD(hh,12,TableB.[Date]+TableB.[Time]) as Date) 
    where tableAx.MaxTemp is null 
    group by tableAx.Date

или вставка новых дат

    insert into tableA (date, MaxTemp) 
    select CAST(DATEADD(hh,12,TableB.[Date]+TableB.[Time]), as Date) as [date] , MAX(TableB.HighTemp) as [MaxTemp]
    from tableA as tableAx
    right outer join TableB 
    on tableAx.Date = CAST(DATEADD(hh,12,TableB.[Date]+TableB.[Time]) as Date) 
    where TableB.Date is null 
    group by CAST(DATEADD(hh,12,TableB.[Date]+TableB.[Time]) as Date)
...