如何提高此SQL查询的速度?

时间:2015-02-03 21:29:57

标签: sql sql-server sql-server-2008 sql-server-2012 sql-server-2008r2-express

我们有两台设备以大约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?

2 个答案:

答案 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的简洁回答(和链接)无疑提供了一些线索。当然,我将不得不推迟这一点,并继续我们最初实施的下一个任务。

感谢所有如此迅速回答我问题的人。