最も近い値を見つけるためにSQL Serverでクエリを作成する方法


15

私はテーブルに次の整数値があるとしましょう

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

OK、リストは続行できます。関係ありません。ここで、このテーブルにクエリを実行し、特定の数のを返しclosest recordsます。numer 32に最も近いレコードを10個返したいとしましょうこれを効率的に達成できますか?

SQL Server 2014にあります。

回答:


21

列にインデックスが付けられていると仮定すると、以下は合理的に効率的です。

10行のシークが2回行われ、その後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

ここに画像の説明を入力してください

NB:上記の実行計画は単純なテーブル定義用でした

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

技術的には、下のブランチのソートもDiffによって順序付けられるため、必要ありません。2つの順序付けられた結果をマージすることは可能です。しかし、私はその計画を得ることができませんでした。

クエリには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つのブランチだけでマージプランを取得するのは難しい場合があります。

  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

Martinの2番目の提案の改良:

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ᵀᴹ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.