在where子句中使用DATEADD的TSQL性能问题

时间:2011-09-19 18:57:05

标签: sql-server tsql sql-server-express

我有一个使用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)中,我们存储了一整天的一些数据,其中一天是从NOON(中午12点)到中午(不是午夜到午夜)。

所以表B列包括: 日期(仅限日期没有时间)
ShiftManager
MaxTemp(这是从中午到中午第二天开始的整个24小时的最高温度)
MinTemp

我得到了包含所有数据的表B,只需要使用表A
来更新MaxTemp和MinTemp 例如:对于09/18/2011,我需要在09/18/2011 12PM和09/19/2011 12PM之间的最大温度读数。
在我们上面的TableA样本中,回归结果将是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 个答案:

答案 0 :(得分:3)

列上的函数通常会破坏性能。 OR也可以。

但是,我认为你想要AND而不是OR,因为它是一个范围。

所以,应用一些逻辑并只进行一次计算

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

(Date + Time - 0.5)将从中午变为午夜到午夜(0.5 = 12小时)。更重要的是,您可以将其设为computed column and index it

更准确地说,Date + Time - 0.5DATEADD(hour, -12, Date+Time),假设DateTime是实际日期/时间而非varchar ...

编辑:这个答案错误,但我会把它留作“不该做什么”

请参阅此内容以获取更多信息:

答案 1 :(得分:1)

如果您使用单个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秒都不重要。

答案 2 :(得分:0)

根据日期,您可能需要使用-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加上时间直接放在DateTime字段中会获得多少。无法远离DATEADD,即使进入函数的参数被索引,函数的输出也不会被索引。您可能能够创建一个计算列= date + time +/- 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)