So schreiben Sie eine Abfrage in SQL Server, um die nächsten Werte zu finden


15

Angenommen, ich habe die folgenden ganzzahligen Werte in einer Tabelle

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

OK, die Liste kann fortgesetzt werden. es spielt keine rolle. Jetzt möchte ich diese Tabelle abfragen und eine bestimmte Anzahl von zurückgeben closest records. Angenommen , ich möchte 10 Übereinstimmungen mit der Zahl 32 zurückgeben. Kann ich dies effizient erreichen?

Es ist in SQL Server 2014.

Antworten:


21

Unter der Annahme, dass die Spalte indexiert ist, sollte das Folgende einigermaßen effizient sein.

Mit zwei Suchläufen von 10 Zeilen und dann einer Art von (bis zu) 20 zurückgegeben.

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 

(dh möglicherweise so etwas wie das unten)

Bildbeschreibung hier eingeben

Oder eine andere Möglichkeit (die die Anzahl der sortierten Zeilen auf maximal 10 reduziert)

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

Bildbeschreibung hier eingeben

Hinweis: Der oben angegebene Ausführungsplan betraf die einfache Tabellendefinition

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

Technisch gesehen sollte die Sortierung im unteren Zweig ebenfalls nicht erforderlich sein, da auch diese von Diff sortiert wird und es möglich wäre, die beiden geordneten Ergebnisse zusammenzuführen. Aber ich konnte diesen Plan nicht bekommen.

Die Abfrage hat ORDER BY Diff ASC, YourCol ASCund nicht nur ORDER BY YourCol ASC, weil genau das dazu geführt hat , dass die Sortierung im obersten Zweig des Plans entfernt wurde. Ich musste die sekundäre Spalte hinzufügen (obwohl dies das Ergebnis nie ändern YourColwird, da es für alle Werte mit demselben Diff gleich ist), damit es die Zusammenführungsverknüpfung (Verkettung) durchlaufen würde, ohne eine Sortierung hinzuzufügen.

SQL Server scheint darauf schließen zu können, dass ein Index für X, der in aufsteigender Reihenfolge gesucht wird, Zeilen liefert, die nach X + Y sortiert sind, und keine Sortierung erforderlich ist. Es kann jedoch nicht abgeleitet werden, dass das Durchlaufen des Index in absteigender Reihenfolge Zeilen in derselben Reihenfolge wie YX (oder sogar nur unäres minus X) liefert. Beide Zweige des Plans verwenden einen Index, um eine Sortierung zu vermeiden. Die Zweige TOP 10im unteren Zweig werden dann sortiert Diff(obwohl sie sich bereits in dieser Reihenfolge befinden), um sie für die Zusammenführung in der gewünschten Reihenfolge abzurufen.

Für andere Abfragen / Tabellendefinitionen ist es möglicherweise schwieriger oder nicht möglich, den Zusammenführungsplan mit nur einer Art von Zweig abzurufen, da SQL Server darauf angewiesen ist, einen Sortierausdruck zu finden:

  1. Akzeptiert, dass die Indexsuche die angegebene Reihenfolge liefert, sodass keine Sortierung vor dem Anfang erforderlich ist .
  2. Ist gerne in der Merge-Operation zu verwenden, erfordert also keine Sortierung nach dem TOP

1

Ich bin etwas verwundert und überrascht, dass wir in diesem Fall Union machen müssen. Das Folgen ist einfach und effizienter

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

Im Folgenden finden Sie den vollständigen Code und den Ausführungsplan, in denen beide Abfragen verglichen werden

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 

Ausführungsplan Vergleich


-3

Verfeinerung von Martins zweitem Vorschlag:

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

2
Es kann ein bisschen einfacher Code sein, aber es wird viel weniger effizient sein. Wir könnten es sogar SELECT TOP 10 * FROM YourTable ORDER BY ABS(YourCol - 32) ;noch einfacher gebrauchen . Auch nicht effizient.
ypercubeᵀᴹ
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.