我有一个表,其中包含有关AutoData
具有组合聚集键Cas
(DateTime) + GCom
(Car ID) 的汽车的历史数据。一条记录包含各种指标,如燃油油位、车辆状态等。
表中一辆车的各个记录之间的间隔AutoData
是不规则的,有时是 120 秒,有时是几秒,有时是几小时等。我需要对记录进行规范化查看,以便每 30 秒显示一条记录。
我有以下脚本:
DECLARE @GCom int = 2563,
@Od DateTime2(0) = '20170210',
@Do DateTime2(0) = '20170224'
--Create a table with intervals by 30 seconds
declare @temp Table ([cas] datetime2(0))
INSERT @temp([cas])
SELECT d
FROM
(
SELECT
d = DATEADD(SECOND, (rn - 1)*30, @Od)
FROM
(
SELECT TOP (DATEDIFF(MINUTE, @Od, @Do)*2)
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM
sys.all_objects AS s1
CROSS JOIN
sys.all_objects AS s2
ORDER BY
s1.[object_id]
) AS x
) AS y;
--Create temp table
CREATE TABLE #AutoData (
[Cas] [datetime2](0) NOT NULL PRIMARY KEY,
[IDProvozniRezim] [tinyint] NOT NULL,
[IDRidic] [smallint] NULL,
[Stav] [tinyint] NOT NULL,
[Klicek] [bit] NOT NULL,
[Alarm] [bit] NOT NULL,
[MAlarm] [tinyint] NOT NULL,
[DAlarm] [bit] NOT NULL,
[Bypass] [bit] NOT NULL,
[Lat] [real] NULL,
[Lon] [real] NULL,
[ObjemAktualni] [real] NOT NULL,
[RychlostMaxV1] [real] NOT NULL,
[RychlostV2] [real] NOT NULL,
[Otacky] [smallint] NOT NULL,
[Nadspotreba] [real] NOT NULL,
[Vzdalenost] [real] NOT NULL,
[Motor] [smallint] NOT NULL
)
--Populate the temp table selecting only relevant AutoData records
INSERT INTO #AutoData
SELECT [Cas]
,[IDProvozniRezim]
,[IDRidic]
,[Stav]
,[Klicek]
,[Alarm]
,[MAlarm]
,[DAlarm]
,[Bypass]
,[Lat]
,[Lon]
,[ObjemAktualni]
,[RychlostMaxV1]
,[RychlostV2]
,[Otacky]
,[Nadspotreba]
,[Vzdalenost]
,[Motor]
FROM AutoData a
WHERE a.GCom = @GCom AND a.cas BETWEEN @Od AND @do
--Select final data
SELECT t.cas, ad.malarm, ad.IDProvoznirezim, ad.Otacky, ad.motor, ad.objemAktualni, ad.Nadspotreba
FROM @temp t
OUTER APPLY (
SELECT TOP 1 stav, malarm, otacky,motor, objemAktualni, Nadspotreba, IDProvoznirezim FROM #AutoData a
WHERE DATEDIFF(SECOND, a.cas, t.cas)<=CASE WHEN Motor>120 THEN Motor ELSE 120 END
AND DATEDIFF(SECOND, a.cas, t.cas)>-30
ORDER BY CASE WHEN DATEDIFF(SECOND, a.cas, t.cas)>0 THEN DATEDIFF(SECOND, a.cas, t.cas) ELSE (DATEDIFF(SECOND, a.cas, t.cas)*-1) +120 END
) ad
DROP TABLE #AutoData
起初,我尝试使用只有一个表变量@temp 将条件WHERE a.GCom = @GCom AND a.cas BETWEEN @Od AND @do
放在最后一个选择中来编写脚本。该脚本需要 39 秒才能执行。
当我使用#AutoData
临时表在临时表中预加载数据子集时,如上面的脚本所示,它下降到 5 秒。
然后我尝试使用表变量@AutoData
而不是#AutoData
- 但它又花了更长的时间 - 22秒。
@temp
对于这个例子,表有 40320 条记录,#AutoData
表有 1904 条记录。但令人惊讶的是,仅使用#temp
表而不是@temp
变量会使执行再次变慢。
我很惊讶地看到使用或不使用临时表/变量的这种差异。显然,SQL Server 本身无法优化 OUTER APPLY 子句的内部。
但是为什么使用表变量和临时表会有这么大的区别呢?有没有其他方法可以知道,使用什么而不只是尝试它?
关键在于您问题的这一部分:
在执行计划中,将鼠标悬停在@temp 表的扫描上。比较估计的行数和实际的行数。(如果您想在http://PasteThePlan.com上发布该计划,我们可以为您提供更具体的细节。免责声明:这是我公司的网站。)
您将看到估计的行数非常低。
SQL Server 估计 1-3 行将从表变量中返回(取决于您的 SQL Server 版本、基数估计器、跟踪标志等)。这反过来给您一个非常糟糕的执行计划,因为 SQL Server 低估了多少工作它需要从其他表中提取多少内存,等等。
以下是获得更准确估计的两种最流行的方法:
要看到我现场直播,请观看 1 小时Watch Brent Tune Queries(免责声明:就是我,链接到我的视频),我在其中使用表变量进行 Stack Overflow 查询,并在前面进行现场调整SQL Rally 挪威的观众。
查询计划器使用#temp 更有效。在表变量上,它只考虑前几行。
您的表变量(以及 #temp 如果您使用一个)可能会受益于声明主键。
在#AutoData 上放一个键,只填充必要的行。
在添加行时按键排序。
我怀疑下面可以用 row_number() 进行优化
这是作为 row_number() 的尝试