Como escrever uma consulta no SQL Server para encontrar os valores mais próximos

15

Digamos que eu tenho os seguintes valores inteiros em uma tabela

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

OK, a lista pode continuar; Não importa. Agora eu quero consultar esta tabela e quero retornar um certo número de closest records. Digamos que eu queira devolver 10 correspondências de registros mais próximas ao número 32. Posso conseguir isso com eficiência?

Está no SQL Server 2014.

MonsterMMORPG
fonte

Respostas:

21

Supondo que a coluna esteja indexada, o seguinte deve ser razoavelmente eficiente.

Com duas buscas de 10 linhas e, em seguida, uma espécie de (até) 20 retornadas.

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 

(ou seja, potencialmente algo como o abaixo)

insira a descrição da imagem aqui

Ou outra possibilidade (que reduz o número de linhas classificadas para no máximo 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

insira a descrição da imagem aqui

NB: O plano de execução acima foi para a definição de tabela simples

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

Tecnicamente, a Classificação na ramificação inferior não deve ser necessária, pois também é solicitada por Diff, e seria possível mesclar os dois resultados ordenados. Mas não consegui esse plano.

A consulta tem ORDER BY Diff ASC, YourCol ASCe não apenasORDER BY YourCol ASC , porque foi isso que acabou trabalhando para se livrar da Classificação na parte superior do plano. Eu precisava adicionar a coluna secundária (mesmo que ela nunca mude o resultado, pois YourColserá o mesmo para todos os valores com o mesmo Diff), para que ela passe pela junção de mesclagem (concatenação) sem adicionar uma Classificação.

O SQL Server parece capaz de inferir que um índice no X procurado em ordem crescente entregará linhas ordenadas por X + Y e nenhuma classificação é necessária. Mas não é possível inferir que a deslocação do índice em ordem decrescente fornecerá linhas na mesma ordem que YX (ou mesmo apenas um menos menos X). Ambas as ramificações do plano usam um índice para evitar uma classificação, mas a TOP 10ramificação inferior é classificada porDiff (mesmo que já estejam nessa ordem) para obtê-las na ordem desejada para a mesclagem.

Para outras consultas / definições de tabela, pode ser mais complicado ou impossível obter o plano de mesclagem com apenas uma espécie de uma ramificação - pois se baseia em encontrar uma expressão de ordenação que o SQL Server:

  1. Aceita que a busca do índice forneça a ordem especificada para que nenhuma classificação seja necessária antes da parte superior.
  2. Tem o prazer de usar na operação de mesclagem, portanto não requer classificação após o TOP
Martin Smith
fonte
1

Estou um pouco intrigado e surpreso por termos de fazer a União neste caso. Seguir é simples e mais eficiente

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

A seguir, é apresentado o plano completo de código e execução, comparando as duas 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 

Comparação do plano de execução

Pushkar Aditya
fonte
-3

Refinamento da segunda sugestão de Martin:

WITH AB
     AS (SELECT *, ABS(32 - YourCol) AS Offset
         FROM   YourTable),
SELECT TOP 10 *
FROM   AB
ORDER  BY Offset ASC
20c
fonte
2
Pode ser um código um pouco mais simples, mas será muito menos eficiente. Poderíamos até usar SELECT TOP 10 * FROM YourTable ORDER BY ABS(YourCol - 32) ;ainda mais simples. Também não é eficiente.
ypercubeᵀᴹ