如何在SQL Server中编写查询以查找最接近的值


16

假设我在表格中有以下整数值

32
11
15
123
55
54
23
43
44
44
56
23

好,列表可以继续;没关系 现在,我要查询该表,并想返回一定数量的closest records。假设我想返回10个最接近的记录匹配数字32。我可以有效地实现这一点吗?

它在SQL Server 2014中。

Answers:


21

假设该列已建立索引,则以下内容应相当有效。

两次搜索10行,然后返回某种(最多)20行。

WITH CTE
     AS ((SELECT TOP 10 *
          FROM   YourTable
          WHERE  YourCol > 32
          ORDER  BY YourCol ASC)
         UNION ALL
         (SELECT TOP 10 *
          FROM   YourTable
          WHERE  YourCol <= 32
          ORDER  BY YourCol DESC))
SELECT TOP 10 *
FROM   CTE
ORDER  BY ABS(YourCol - 32) ASC 

(即可能类似于以下内容)

在此处输入图片说明

或另一种可能性(将排序的行数减少到最大10)

WITH A
     AS (SELECT TOP 10 *,
                       YourCol - 32 AS Diff
         FROM   YourTable
         WHERE  YourCol > 32
         ORDER  BY Diff ASC, YourCol ASC),
     B
     AS (SELECT TOP 10 *,
                       32 - YourCol AS Diff
         FROM   YourTable
         WHERE  YourCol <= 32
         ORDER  BY YourCol DESC),
     AB
     AS (SELECT *
         FROM   A
         UNION ALL
         SELECT *
         FROM   B)
SELECT TOP 10 *
FROM   AB
ORDER  BY Diff ASC

在此处输入图片说明

注意:上面的执行计划是针对简单表定义的

CREATE TABLE [dbo].[YourTable](
    [YourCol] [int] NOT NULL CONSTRAINT [SomeIndex] PRIMARY KEY CLUSTERED 
)

从技术上讲,也不需要底部分支上的Sort,因为Diff也对它进行排序,并且可以合并两个排序的结果。但是我没有那个计划。

该查询具有ORDER BY Diff ASC, YourCol ASC,而不仅仅是ORDER BY YourCol ASC,因为最终摆脱了计划顶部分支中的“排序”。我需要在其中添加辅助列(即使它永远不会更改结果,因为YourCol对于具有相同Diff的所有值都将是相同的),因此它将通过合并联接(并置)而不添加排序。

SQL Server似乎能够推断按升序查找的X索引将传递按X + Y排序的行,并且不需要排序。但是,无法推断出以降序移动索引将以与YX相同的顺序传递行(甚至只是一元减X)。计划的两个分支都使用索引来避免排序,但是对TOP 10底部分支中的进行排序Diff(即使它们已经按该顺序排序),以使其按合并所需的顺序进行排序。

对于其他查询/表定义,仅通过一个分支即可获得合并计划可能比较棘手,甚至无法实现-因为它依赖于查找SQL Server的排序表达式:

  1. 接受索引查找将提供指定的顺序,因此在顶部之前不需要排序。
  2. 很高兴在合并操作中使用,因此在排序后不需要排序 TOP

1

对于在这种情况下必须进行联盟,我感到有些困惑和惊讶。跟随简单而高效

SELECT TOP (@top) *
FROM @YourTable
ORDER BY ABS(YourCol-@x)

以下是比较这两个查询的完整代码和执行计划

DECLARE @YourTable TABLE (YourCol INT)
INSERT @YourTable (YourCol)
VALUES  (32),(11),(15),(123),(55),(54),(23),(43),(44),(44),(56),(23)

DECLARE @x INT = 100, @top INT = 5

--SELECT TOP 100 * FROM @YourTable
SELECT TOP (@top) *
FROM @YourTable
ORDER BY ABS(YourCol-@x)

;WITH CTE
     AS ((SELECT TOP 10 *
          FROM   @YourTable
          WHERE  YourCol > 32
          ORDER  BY YourCol ASC)
         UNION ALL
         (SELECT TOP 10 *
          FROM   @YourTable
          WHERE  YourCol <= 32
          ORDER  BY YourCol DESC))
SELECT TOP 10 *
FROM   CTE
ORDER  BY ABS(YourCol - 32) ASC 

执行计划比较


-3

提炼马丁的第二个建议:

WITH AB
     AS (SELECT *, ABS(32 - YourCol) AS Offset
         FROM   YourTable),
SELECT TOP 10 *
FROM   AB
ORDER  BY Offset ASC

2
它可能是更简单的代码,但是效率将大大降低。我们甚至可以使用SELECT TOP 10 * FROM YourTable ORDER BY ABS(YourCol - 32) ;更简单的方法。也不高效。
ypercubeᵀᴹ
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.