Cómo escribir una consulta en SQL Server para encontrar los valores más cercanos


16

Digamos que tengo los siguientes valores enteros en una tabla

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

OK, la lista puede continuar; No importa. Ahora quiero consultar esta tabla y quiero devolver un cierto número de closest records. Digamos que quiero devolver 10 coincidencias de registro más cercanas al número 32. ¿Puedo lograr esto de manera eficiente?

Está en SQL Server 2014.

Respuestas:


21

Suponiendo que la columna está indexada, lo siguiente debería ser razonablemente eficiente.

Con dos búsquedas de 10 filas y luego una especie de (hasta) 20 devueltas.

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 

(es decir, potencialmente algo como lo siguiente)

ingrese la descripción de la imagen aquí

U otra posibilidad (que reduce el número de filas ordenadas a un máximo de 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

ingrese la descripción de la imagen aquí

NB: el plan de ejecución anterior era para la definición de tabla simple

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

Técnicamente, la Clasificación en la rama inferior tampoco debería ser necesaria, ya que también lo ordena Diff, y sería posible fusionar los dos resultados ordenados. Pero no pude conseguir ese plan.

La consulta tiene ORDER BY Diff ASC, YourCol ASCy no solo ORDER BY YourCol ASC, porque eso fue lo que terminó trabajando para deshacerse de la Clasificación en la rama superior del plan. Necesitaba agregar la columna secundaria (aunque nunca cambiará el resultado comoYourCol que será el mismo para todos los valores con el mismo Diff), por lo que pasaría por la combinación de fusión (concatenación) sin agregar un Ordenar.

SQL Server parece inferir que un índice en X buscado en orden ascendente entregará filas ordenadas por X + Y y no es necesario ordenarlas. Pero no puede inferir que recorrer el índice en orden descendente generará filas en el mismo orden que YX (o incluso solo unario menos X). Ambas ramas del plan usan un índice para evitar una clasificación, pero las TOP 10de la rama inferior se ordenan por Diff(aunque ya estén en ese orden) para obtener el orden deseado para la fusión.

Para otras consultas / definiciones de tabla, puede ser más complicado o imposible obtener el plan de fusión con solo una especie de rama, ya que se basa en encontrar una expresión de orden que SQL Server:

  1. Acepta que la búsqueda de índice proporcionará el orden especificado, por lo que no se necesita ninguna clasificación antes de la parte superior.
  2. Es feliz de usar en la operación de fusión, por lo que no requiere ningún tipo después TOP

1

Estoy un poco perplejo y sorprendido de que tengamos que hacer Unión en este caso. Lo siguiente es simple y más eficiente

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

El siguiente es el código completo y el plan de ejecución que compara ambas consultas

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 

Comparación del plan de ejecución


-3

Refinamiento de la segunda sugerencia de Martin:

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

2
Puede ser un código un poco más simple, pero será mucho menos eficiente. Incluso podríamos usar SELECT TOP 10 * FROM YourTable ORDER BY ABS(YourCol - 32) ;aún más simple. Tampoco eficiente.
ypercubeᵀᴹ
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.