Как написать запрос в SQL Server, чтобы найти ближайшие значения

16

Допустим, у меня есть следующие целочисленные значения в таблице

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

ОК, список можно продолжать; это не важно Теперь я хочу запросить эту таблицу, и я хочу вернуть определенное количество closest records. Допустим, я хочу вернуть 10 ближайших совпадений записей в число 32. Могу ли я добиться этого эффективно?

Это в SQL Server 2014.

MonsterMMORPG
источник

Ответы:

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

введите описание изображения здесь

NB: План выполнения выше был для простого определения таблицы

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

Технически сортировка в нижней ветви также не требуется, так как это тоже заказано 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
20c
источник
2
Это может быть немного более простой код, но он будет гораздо менее эффективным. Мы могли бы даже использовать SELECT TOP 10 * FROM YourTable ORDER BY ABS(YourCol - 32) ;еще более простой. Также не эффективен.
ypercubeᵀᴹ