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.
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)
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
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:
TOP
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