Come scrivere una query in SQL Server per trovare i valori più vicini


16

Diciamo che ho i seguenti valori interi in una tabella

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

OK, l'elenco può continuare; non importa. Ora voglio interrogare questa tabella e voglio restituire un certo numero di closest records. Diciamo che voglio restituire 10 numeri record più vicini al numero 32. Posso raggiungere questo obiettivo in modo efficiente?

È in SQL Server 2014.

Risposte:


21

Supponendo che la colonna sia indicizzata, quanto segue dovrebbe essere ragionevolmente efficiente.

Con due ricerche di 10 righe e quindi una sorta di (fino a) 20 restituiti.

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 

(ovvero potenzialmente qualcosa di simile al seguente)

inserisci qui la descrizione dell'immagine

O un'altra possibilità (che riduce il numero di righe ordinate a max 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

inserisci qui la descrizione dell'immagine

NB: il piano di esecuzione sopra era per la semplice definizione della tabella

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

Tecnicamente, l'ordinamento sul ramo inferiore non dovrebbe essere necessario, poiché anche quello è ordinato da Diff e sarebbe possibile unire i due risultati ordinati. Ma non sono stato in grado di ottenere quel piano.

La query ha ORDER BY Diff ASC, YourCol ASCe non solo ORDER BY YourCol ASC, perché è quello che ha funzionato per sbarazzarsi dell'ordinamento nel ramo superiore del piano. Avevo bisogno di aggiungere la colonna secondaria (anche se non cambierà mai il risultato poiché YourColsarà lo stesso per tutti i valori con lo stesso Diff) in modo da passare attraverso l'unione di unione (concatenazione) senza aggiungere un ordinamento.

SQL Server sembra in grado di dedurre che un indice su X cercato in ordine crescente recapiterà le righe ordinate da X + Y e non è necessario alcun ordinamento. Ma non è in grado di dedurre che viaggiare l'indice in ordine decrescente recapiterà le righe nello stesso ordine di YX (o anche solo unario meno X). Entrambi i rami del piano utilizzano un indice per evitare un ordinamento ma i TOP 10rami in basso vengono quindi ordinati per Diff(anche se sono già in quell'ordine) per farli nell'ordine desiderato per l'unione.

Per altre definizioni di query / tabelle potrebbe essere più complicato o meno possibile ottenere il piano di unione con una sorta di ramo - poiché si basa sulla ricerca di un'espressione di ordinamento che SQL Server:

  1. Accetta che l'indice cerca fornirà l'ordine specificato, quindi non è necessario alcun ordinamento prima dell'inizio .
  2. È felice di utilizzare l'operazione di unione, quindi non richiede alcun ordinamento dopo il TOP

1

Sono un po 'perplesso e sorpreso che in questo caso dobbiamo fare l'Unione. Di seguito è semplice e più efficiente

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

Di seguito è riportato il codice completo e il piano di esecuzione che confronta entrambe le query

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 

Confronto del piano di esecuzione


-3

Perfezionamento del secondo suggerimento di Martin:

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

2
Potrebbe essere un codice un po 'più semplice, ma sarà molto meno efficiente. Potremmo persino usare SELECT TOP 10 * FROM YourTable ORDER BY ABS(YourCol - 32) ;ancora più semplice. Neanche efficiente.
ypercubeᵀᴹ
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.