Search code examples
sqlsql-server-2008query-optimizationcovering-index

efficient "find nearest number or date" in SQL where date/number column is covered by an index


Using SQL2008, I'm trying to figure out an efficient query to find a row whose date is nearest to a specific target date.

There are obvious inefficient solutions (e.g. table scan using ABS and DATEDIFF) which I didn't bother looking at, because my table already has a covering index where the date is the first column. I can narrow down the results using that index before figuring out exactly which row is nearest.

In theory I should be able to satisfy the query using a single index lookup and then a sequential pull of 2 rows of data from that index.

But so far I've been unable to find a more optimal solution than this one:

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))

This is fast (4 logical reads on the test schema below, 9 logical reads in my real-world table) but it's still a 2-scan-count solution. Is there a more efficient solution which only hits this index once?

Or is my existing solution "good enough" because that second index seek will be pulling cached pages accessed by the first seek, meaning it will be so fast that further optimzation (even if possible) will yield minimal actual perf improvement?

Here's the schema and some sample data. Both are simplified from my actual schema, although the resulting query plan is the same as my more complex table:

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

Solution

  • Consider first determining where in the table your @target is, then limiting the scope of your search for +1 / -1 to within a day or a week first. Then sorting by date within that set to find the closest will cost less than applying TOP 1/ORDER BY to the whole set on each side.