SQL 中高效的“查找最近的数字或日期”,其中日期/数字列被索引覆盖

Posted

技术标签:

【中文标题】SQL 中高效的“查找最近的数字或日期”,其中日期/数字列被索引覆盖【英文标题】:efficient "find nearest number or date" in SQL where date/number column is covered by an index 【发布时间】:2011-07-19 00:30:42 【问题描述】:

使用 SQL2008,我试图找出一个有效的查询来查找日期最接近特定目标日期的行。

有一些明显低效的解决方案(例如使用ABS and DATEDIFF 的表扫描),我没有费心去看,因为我的表已经有一个覆盖索引,其中日期是第一列。在准确确定最近的行之前,我可以使用该索引缩小结果范围。

理论上,我应该能够使用单个索引查找来满足查询,然后从该索引中顺序提取 2 行数据。

但到目前为止,我还没有找到比这个更优化的解决方案:

DECLARE @target DATETIME = '01/02/2011'

SELECT TOP 1 Val, Measured
FROM (
   SELECT TOP 1 Val, Measured 
       FROM tbl 
       WHERE Measured <= @Target 
       ORDER BY Measured desc
   UNION ALL
   SELECT TOP 1 Val, Measured 
       FROM tbl 
       WHERE Measured >= @Target 
       ORDER BY Measured asc
) x
ORDER BY ABS (DATEDIFF (second, Measured, @Target))

这很快(在下面的测试模式中进行 4 次逻辑读取,在我的真实表中进行 9 次逻辑读取),但它仍然是 2 次扫描计数的解决方案。是否有更有效的解决方案只命中该索引一次?

或者我现有的解决方案是否“足够好”,因为第二次索引搜索将拉取第一次搜索访问的缓存页面,这意味着它会非常快,以至于进一步优化(即使可能)将产生最小的实际性能改进?

这是架构和一些示例数据。两者都是从我的实际架构中简化的,尽管生成的查询计划与我更复杂的表相同:

CREATE TABLE tbl
(
    ID int IDENTITY(1,1) PRIMARY KEY CLUSTERED NOT NULL,
    Measured DATETIME NOT NULL,
    Val int NOT NULL
);
CREATE NONCLUSTERED INDEX IX_tbl ON tbl (Measured) INCLUDE (Val)
INSERT tbl VALUES ('2011-01-01 12:34',6);
INSERT tbl VALUES ('2011-01-01 23:34',6);
INSERT tbl VALUES ('2011-01-03 09:03',12);
INSERT tbl VALUES ('2011-02-01 09:24',18);
INSERT tbl VALUES ('2011-02-08 07:12',7);
INSERT tbl VALUES ('2011-03-01 12:34',6);
INSERT tbl VALUES ('2011-04-03 09:03',12);
INSERT tbl VALUES ('2011-05-01 09:24',18);
INSERT tbl VALUES ('2011-06-08 07:12',7);
-- insert another few million rows here to compare to my real-world table

【问题讨论】:

SELECT MAX (measured) FROM tbl WHERE measured &lt; '01/02/2011' UNION SELECT MIN (measured) FROM tbl WHERE measured &gt; '01/02/2011' ... 在性能方面实际上是相同的,还是效率更低? 您在抱怨 9 次逻辑读取?如果这是您遇到的最大性能问题,那么您可能有太多空闲时间。 :-) 也就是说,还有一些其他潜在的想法,但需要更多的元数据。例如,您是否将在此表中有足够的条目以确保您每天至少有一行? @target 是否总是一个完整的日期? @user unknown,MAX/MIN 的表现与 TOP 1 大致相同,除了 MAX 你不能轻易地同时获得 Val。 @Aaron Bertrand - \@target 通常不会是一个完整的日期。大多数日子会有 1-3 行,但有时我会连续几天没有行。不过,我知道您要去哪里:我可以选择一个日期范围然后对其进行过滤吗?这是一个好主意,特别是如果我需要的最小时间窗口(可能在任一侧一周或两周)可以放在一个页面中。想提交作为答案吗?回复:我手头的时间太多,查询将在运行数千次的 OUTER APPLY 中进行,因此在此处保存一些读取可以从整个查询中减少大量 I/O。 当然,如果您认为我已经将您引导到正确的方向,我可以做到。 【参考方案1】:

首先考虑确定@target 在表中的哪个位置,然后首先将 +1 / -1 的搜索范围限制在一天或一周内。然后在该集合中按日期排序以找到最接近的成本将低于将 TOP 1/ORDER BY 应用于每一侧的整个集合。

【讨论】:

以上是关于SQL 中高效的“查找最近的数字或日期”,其中日期/数字列被索引覆盖的主要内容,如果未能解决你的问题,请参考以下文章

创建一个表,其中动态 PL/SQL 中的“日期条件”

ORACLE中高效SQL的写法

SQL 其中 datetime 列等于今天的日期?

SQL Select 查询以选择日期之间的数据,其中从日期月份高于日期月份和年份差异>1

SQL查询其中日期=今天减去7天

NodeJS高效数据库日期超时事件。