尝试优化选择“近似最接近记录”的查询

时间:2012-02-16 07:07:47

标签: sql-server sql-server-2005 tsql

我有一个包含大量数据的表格,我们特别关注date字段。原因是数据量上升了大约30倍,旧的方式很快就会崩溃。我希望您可以帮助我优化需求的查询:

  • 获取日期列表(由基于cte的表值函数生成)
  • 为每个日期检索单个记录
    • 基于'最近'的某些定义

例如,当前表包含5秒(+/-一点点)间隔的数据。我需要对该表进行采样并获得最接近30秒间隔的记录。

我现在所做的工作得很好。我只是好奇是否有办法更优化它。如果我能在Linq To SQL中做到这一点,那也是很好的。考虑到日期值的数量(约200万行最小值),我甚至对索引的建议感兴趣。

declare @st  datetime ; set @st  = '2012-01-31 05:05:00';
declare @end datetime ; set @end = '2012-01-31 05:10:00';

select distinct
    log.*   -- id, 
from 
    dbo.fn_GenerateDateSteps(@st, @end, 30) as d
        inner join lotsOfLogData log on l.Id = (
            select top 1 e.[Id]
            from 
                lotsOfLogData as log  -- contains data in 5 second intervals
            where
                log.stationId = 1000 
                -- search for dates in a certain range
                AND utcTime between DateAdd(s, -10, dt) AND DateAdd(s, 5, dt)
            order by
                -- get the 'closest'. this can change a little, but will always 
                -- be based on a difference between the date
                abs(datediff(s, dt, UtcTime)) 
        )
    -- updated the query to be correct. stadionId should be inside the subquery

lotsOfLogData的表结构如下。站点ID(可能是50个)相对较少,但每个站点都有很多记录。我们查询时知道了站号。

create table ##lotsOfLogData (
    Id          bigint      identity(1,1) not null
,   StationId   int         not null
,   UtcTime     datetime    not null
    -- 20 other fields, used for other calculations
)
对于给定的参数,

fn_GenerateDateSteps 会返回这样的数据集:

[DT]
2012-01-31 05:05:00.000
2012-01-31 05:05:30.000
2012-01-31 05:06:00.000
2012-01-31 05:06:30.000  (and so on, every 30 seconds)

我也用这种方式用临时表做了这个,但是这样做的费用稍贵一点。

declare @dates table ( dt datetime, ClosestId bigint); 
insert into @dates (dt) select dt from dbo.fn_GenerateDateSteps(@st, @end, 30)
update @dates set closestId = ( -- same subquery as above )
select * from lotsOfLogData inner join @dates on Id = ClosestId

编辑:已修复

现在可以使用200K +行了。我尝试了两种方式,交叉应用适当的索引(id / time + includes(..所有列......)工作正常。但是,我最终得到了我开始的查询,使用更简单(和现有)关于[id + time]的索引。更容易理解的查询是我为什么选择那个问题的原因。也许还有更好的方法可以做到,但我看不到它:D

-- subtree cost (crossapply) : .0808
-- subtree cost (id based)   : .0797

-- see above query for what i ended up with

2 个答案:

答案 0 :(得分:1)

只是一些想法...不会真的称之为答案,但它对于评论框来说太大了。

首先,如果你还没有这样做,我会查看查询的执行计划。

更深奥:您是否可以选择将日期表示为原始值(如表示明确定义时间后的秒/分钟整数)?即使我相信SQL Server将日期存储为数字值,但对原语的操作可能会快一些,因为它会消除对DateAdd()DateDiff()的重复调用。

This (fairly old) article给出了SQL Server实际存储日期的示例。也许您可以将日期保留为DATETIME,但可以使用基本数学对其进行操作。

无论数据类型如何,我都会在日期列上尝试聚集索引,因为您的搜索可能会受益于聚簇索引提供的物理排序,尤其是在较窄范围内搜索时。再次,执行计划可能会有启发性。

我还可以看到用于表示数据的星型模式,其日期维度包含日期概括。然后,您可以搜索一般化。即使没有使用概括,实际的日期数也会减少,因为具有相同日期的所有事实都可以指向维度中的相同记录,因此日期只需要评估一次。

最后,SQL性能调优向导(我相信它是在2005年,我知道它是在2008年)建议你的查询?我不建议盲目实施其建议,但我经常在它推荐的事情中找到好的想法。

答案 1 :(得分:1)

你可以尝试

  • inner join更改为cross apply
  • where log.stationid移至子选择。

SQL声明

SELECT  DISTINCT log.*   -- id, 
FROM    dbo.fn_GenerateDateSteps(@st, @end, 30) AS d
        CROSS APPLY (
            SELECT  TOP 1 log.*
            FROM    lotsOfLogData AS log  -- contains data in 5 second intervals
            WHERE   -- search for dates in a certain range
                    utcTime between DATEADD(s, -10, d.dt) AND DATEADD(s, 5, d.dt)
                    AND log.stationid = 1000
            ORDER BY
                    -- get the 'closest'. this can change a little, but will always 
                    -- be based on a difference between the date
                    ABS(DATEDIFF(s, d.dt, UtcTime)) 
        ) log