Як написати запит у SQL Server, щоб знайти найближчі значення


16

Скажімо, у мене є такі цілі значення в таблиці

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

Гаразд, список може продовжуватися; це не має значення. Тепер я хочу запитати цю таблицю і хочу повернути певну кількість closest records. Скажімо, я хочу повернути 10 найбільш близьких записів матчу до числа 32. Чи можу я досягти цього ефективно?

Це в SQL Server 2014.

Відповіді:


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

2
Це може бути трохи простіший код, але він буде набагато менш ефективним. Ми могли навіть використовувати SELECT TOP 10 * FROM YourTable ORDER BY ABS(YourCol - 32) ;ще простіші. Не ефективний також.
ypercubeᵀᴹ
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.