我们有两台设备以大约30秒的间隔收集数据。这些设备位于两个间隔很大的位置。每个站点的每个集合的绝对时间可以变化+/- 30秒。有时,网站会因各种原因而脱机。来自每个设备的数据代表不同类型的测量,例如,来自device1的温度和来自device2的湿度。进程将来自device1和device2的数据记录到运行在与每个设备不同的服务器上的SQL Server 2012 Express数据库中的单独表中。
希望将来自两个设备的数据呈现为记录,这些记录将包含具有特定日期/时间的site1值的列,并结合site2的数据(如果有)。然后,用户程序将请求指定日期/时间范围的记录集。为此,我构建了以下SP:
ALTER PROCEDURE [db_datareader].[DataJoinDateRange]
@DateFrom DateTime = '2014-05-15 15:10:24.000',
@DateTo DateTime = '2014-06-15 15:10:24.000'
AS
BEGIN
SET NOCOUNT ON;
WITH site1(id, date_time, dataval)
AS
(
SELECT *
FROM site1_data
WHERE site1_data.date_time BETWEEN @DateFrom AND @DateTo
),
site2(id, date_time, datavaql)
AS
(
SELECT *
FROM site2_data
WHERE site2_data.date_time BETWEEN @DateFrom AND @DateTo
)
SELECT * from site1 site1_res
INNER JOIN (select id, date_time, data_val) site2_res
on ABS(DATEDIFF("SECOND", site1_res.date_time, site_2_res.date_time)) < 30
END
目的是首先选择所需日期/时间范围内的记录,然后将site1中的记录连接到site2中的记录,这些记录在+/- 30秒内。范围。生成的记录集将包含来自两个设备的数据,如果不存在相应的记录,则为空。
这似乎有效:输出具有所需形式的记录,并对应于每个表中的正确记录。但执行速度很慢。对几周日期范围的查询大约需要1分30秒。 Site1在此日期范围内包含约5000条记录,而Site2仅包含1条记录。仅对每个表的日期范围的SELECT查询在一秒钟内执行。
我之前从未深入研究过SQL,但我们的小组目前还没有其他任何人来完成这项任务。谁能让我知道正确的方法,或者至少如何加速这个SP?
答案 0 :(得分:1)
您可以通过更好地使用date_time
列上的索引来尝试改进解决方案。
ABS(S1 - S2) < 30
相当于
ABS(S2 - S1) < 30
<=>
-30 < S2 - S1 < 30
<=>
S2 - S1 < 30
AND
S2 - S1 > -30
<=>
S2 < S1 + 30
AND
S2 > S1 - 30
你真的不需要第一次CTE,尽管不应该受伤。但是,WHERE
中的CROSS APPLY
子句最好这样写。此外,如果要查看site1中没有来自site2的任何相应数据的数据,则应使用OUTER APPLY
而不是CROSS APPLY
。现在site2.date_time
不在函数调用内,优化器可以在此列上使用索引。
ALTER PROCEDURE [dbo].[SPJoinDateRange]
@DateFrom DateTime = '2014-05-01 15:10:24.000',
@DateTo DateTime = '2014-07-31 15:10:00.000'
AS
BEGIN
SET NOCOUNT ON;
SELECT
site1_data.id AS id1
,site1_data.date_time AS date_time1
,site1_data.data_val1
,CA_site2.id2
,CA_site2.date_time2
,CA_site2.data_val2
FROM
site1_data
OUTER APPLY
(
SELECT
site2_data.id as id2
,site2_data.date_time as date_time2
,site2_data.data_val2
FROM
site2_data
WHERE
site2.date_time BETWEEN @DateFrom AND @DateTo
AND site2.date_time < DATEADD(second, +30, site1_data.date_time)
AND site2.date_time > DATEADD(second, -30, site1_data.date_time)
) AS CA_site2
WHERE
site1_data.date_time BETWEEN @DateFrom AND @DateTo
;
END
如果您可以添加一个额外的列,其中包含四舍五入到最接近的30秒的时间戳,它的工作速度会更快。如果您不需要精确的时间戳,请将现有值四舍五入。
如果我们添加一个名为date_time_rounded的列,其中包含舍入为30秒的原始时间戳,在其上创建索引,则查询将如下所示:
ALTER PROCEDURE [dbo].[SPJoinDateRange]
@DateFrom DateTime = '2014-05-01 15:10:24.000',
@DateTo DateTime = '2014-07-31 15:10:00.000'
AS
BEGIN
SET NOCOUNT ON;
SELECT
site1_data.id AS id1
,site1_data.date_time AS date_time1
,site1_data.data_val1
,site2_data.id AS id2
,site2_data.date_time AS date_time2
,site2_data.data_val2
FROM
site1_data
LEFT JOIN site2_data ON site2_data.date_time_rounded = site1_data.date_time_rounded
WHERE
site1_data.date_time BETWEEN @DateFrom AND @DateTo
;
END
要将date_time
四舍五入到最近的30秒,您可以使用以下内容:
DATEADD(second, 30 * ROUND(DATEDIFF(second, '20010101', date_time)/30.0, 0), '20010101')
它计算从2001-01-01
到给定date_time
的秒数,将它们除以30,将结果舍入为整数,将结果乘以30,将此秒数加到2001-01-01
运行几次以查看其工作原理:
SELECT
GETDATE() as original,
DATEADD(second, 30 * ROUND(DATEDIFF(second, '20010101', GETDATE())/30.0, 0), '20010101') AS rounded
答案 1 :(得分:0)
我在其他地方找到this article,这非常有帮助。结果,我将SP改为以下内容:
ALTER PROCEDURE [dbo].[SPJoinDateRange]
@DateFrom DateTime = '2014-05-01 15:10:24.000',
@DateTo DateTime = '2014-07-31 15:10:00.000'
AS
BEGIN
SET NOCOUNT ON;
WITH site1(id, date_time, data_val1)
AS
(
SELECT *
FROM site1_data
WHERE site1_data.date_time BETWEEN @DateFrom AND @DateTo
)
SELECT * FROM site1
CROSS APPLY
(
SELECT id as id1, date_time as date_time1, data_val2
FROM site2_data AS site2
WHERE site2.date_time BETWEEN @DateFrom AND @DateTo
AND
ABS(DATEDIFF("SECOND", site1.date_time, site2.date_time)) < 30
)
AS result
END
此查询的结果时间为6秒。 (相比之前版本的90秒。)这可能仍然比可能的要慢得多;理想情况下,我的下一个任务是理解为什么这种方法更快。 Sean Lange的简洁回答(和链接)无疑提供了一些线索。当然,我将不得不推迟这一点,并继续我们最初实施的下一个任务。
感谢所有如此迅速回答我问题的人。