web-dev-qa-db-fra.com

Comment écrire une requête dans SQL Server pour trouver les valeurs les plus proches

Disons que j'ai les valeurs entières suivantes dans une table

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

OK, la liste peut continuer; ça n'a pas d'importance. Maintenant, je veux interroger cette table et je veux retourner un certain nombre de closest records. Disons Je veux renvoyer 10 correspondances d'enregistrement les plus proches au chiffre 32. Puis-je y parvenir efficacement?

C'est dans SQL Server 2014.

16
MonsterMMORPG

En supposant que la colonne est indexée, les éléments suivants devraient être raisonnablement efficaces.

Avec deux recherches de 10 lignes, puis une sorte de (jusqu'à) 20 retournés.

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 

(c'est-à-dire potentiellement quelque chose comme ci-dessous)

enter image description here

Ou une autre possibilité (qui réduit le nombre de lignes triées à 10 au maximum)

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

enter image description here

NB: Le plan d'exécution ci-dessus était pour la définition de table simple

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

Techniquement, le tri sur la branche inférieure ne devrait pas non plus être nécessaire car cela est également ordonné par Diff, et il serait possible de fusionner les deux résultats ordonnés. Mais je n'ai pas pu obtenir ce plan.

La requête a ORDER BY Diff ASC, YourCol ASC et pas seulement ORDER BY YourCol ASC, car c'est ce qui a fini par s'efforcer de se débarrasser du tri dans la branche supérieure du plan. J'avais besoin d'ajouter la colonne secondaire (même si cela ne changera jamais le résultat car YourCol sera le même pour toutes les valeurs avec le même Diff) afin qu'il passe par la jointure de fusion (concaténation) sans ajouter un tri.

SQL Server semble capable de déduire qu'un index sur X recherché dans l'ordre croissant fournira des lignes ordonnées par X + Y et aucun tri n'est nécessaire. Mais il n'est pas en mesure de déduire que le déplacement de l'index dans l'ordre décroissant fournira des lignes dans le même ordre que Y-X (ou même simplement unaire moins X). Les deux branches du plan utilisent un index pour éviter un tri mais le TOP 10 dans la branche inférieure sont ensuite triés par Diff (même s'ils sont déjà dans cet ordre) pour les obtenir dans l'ordre souhaité pour la fusion.

Pour d'autres requêtes/définitions de table, il peut être plus difficile ou impossible d'obtenir le plan de fusion avec juste une sorte de branche - car il repose sur la recherche d'une expression de classement que SQL Server:

  1. Accepte que la recherche d'index fournira l'ordre spécifié afin qu'aucun tri ne soit nécessaire avant le sommet.
  2. Est heureux d'utiliser dans l'opération de fusion et ne nécessite donc aucun tri après le TOP
21
Martin Smith

Je suis un peu perplexe et surpris que nous devions faire Union dans ce cas. Le suivi est simple et plus efficace

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

Voici le code complet et le plan d'exécution comparant les deux requêtes

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 

Execution plan comparison

1
Pushkar Aditya