SQL Server에서 쿼리를 작성하여 가장 가까운 값을 찾는 방법


16

테이블에 다음 정수 값이 있다고 가정 해 봅시다.

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

좋아, 목록은 계속 될 수있다. 중요하지 않습니다. 이제이 테이블을 쿼리하고 특정 수의을 반환하고 싶습니다 closest records. 10 개의 가장 근접한 레코드를 숫자 32에 반환하려고 한다고 가정 해 봅시다 . 이것을 효율적으로 달성 할 수 있습니까?

SQL Server 2014에 있습니다.

답변:


21

열이 색인화되었다고 가정하면 다음이 합리적으로 효율적이어야합니다.

10 개의 행을 두 번 찾은 다음 일종의 (최대) 20이 리턴되었습니다.

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 

(즉, 잠재적으로 아래와 같은 것)

여기에 이미지 설명을 입력하십시오

또는 다른 가능성 (최대 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

여기에 이미지 설명을 입력하십시오

주의 : 위의 실행 계획은 간단한 테이블 정의를위한 것이 었습니다.

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

기술적으로 아래쪽 분기의 정렬은 Diff에서도 정렬 할 필요가 없으며 두 개의 정렬 된 결과를 병합 할 수 있습니다. 그러나 나는 그 계획을 얻을 수 없었다.

이 쿼리는 계획의 최상위 분기에서 정렬을 제거하기 위해 결국 작동했기 때문에 ORDER BY Diff ASC, YourCol ASC이뿐 만 아니라 ORDER BY YourCol ASC그 결과는 아닙니다 . 보조 열을 추가해야했지만 ( YourCol같은 Diff를 가진 모든 값에 대해 동일한 결과를 변경하지는 않지만 ) 정렬을 추가하지 않고 병합 조인 (연결)을 거칩니다.

SQL Server는 오름차순으로 검색 한 X의 인덱스가 X + Y로 정렬 된 행을 전달하므로 정렬이 필요하지 않다고 추측 할 수 있습니다. 그러나 인덱스를 내림차순으로 이동하면 YX와 동일한 순서로 행을 전달한다고 가정 할 수 없습니다 (또는 단항 마이너스 X). 계획의 두 브랜치는 정렬을 피하기 위해 인덱스를 사용하지만 TOP 10맨 아래 브랜치의 정렬 Diff은 원하는 순서대로 정렬하기 위해 정렬됩니다 (이미 이미 순서대로 정렬되어 있음).

다른 쿼리 / 테이블 정의의 경우 SQL Server에서 정렬 식을 찾는 데 의존하기 때문에 하나의 분기만으로 병합 계획을 얻는 것이 까다 롭거나 불가능할 수 있습니다.

  1. 인덱스 검색이 지정된 순서를 제공하므로 정렬 전에 정렬이 필요하지 않습니다 .
  2. 병합 작업에 사용하면 좋으므로 다음에 정렬 할 필요가 없습니다. TOP

1

우리는이 경우 유니온을해야한다는 것에 약간 당황하고 놀랐습니다. 다음은 간단하고 효율적입니다

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

다음은 두 쿼리를 비교하는 완전한 코드 및 실행 계획입니다.

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 

실행 계획 비교


-3

마틴의 두 번째 제안 개선 :

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

2
좀 더 간단한 코드 일지 모르지만 훨씬 덜 효율적입니다. SELECT TOP 10 * FROM YourTable ORDER BY ABS(YourCol - 32) ;더 간단하게 사용할 수도 있습니다 . 효율적이지 않습니다.
ypercubeᵀᴹ 1
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.