web-dev-qa-db-ja.com

SQL Serverでクエリを記述して最も近い値を見つける方法

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

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

OK、リストは続きます。それは問題ではありません。次に、このテーブルにクエリを実行し、特定の数のclosest recordsを返します。 数値32に最も近い10件のレコード一致を返したいこれを効率的に達成できますか?

SQL Server 2014に含まれています。

16
MonsterMMORPG

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

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 

(つまり、潜在的に以下のようなもの)

enter image description here

または別の可能性(ソートされる行の数を最大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

enter image description here

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

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も含まれています。これは、プランの一番上のブランチでSortを取り除くために機能したためです。 (YourColが同じDiffのすべての値で同じになるため、結果が変更されることはありませんが)セカンダリ列を追加する必要があったため、マージ結合(連結)を行わずにソートを追加します。

SQL Serverは、昇順でシークされたXのインデックスはX + Yで並べられた行を提供し、ソートは不要であると推測できるようです。ただし、降順でインデックスを移動すると、行がY-X(または単項マイナスX)と同じ順序で配信されると推測することはできません。プランの両方のブランチはインデックスを使用してソートを回避しますが、下部のブランチのTOP 10Diffによってソートされ(それらがすでにこの順序になっている場合でも)、目的の順序で取得されますマージのため。

他のクエリ/テーブル定義の場合、SQL Serverの順序付け式の検索に依存しているため、1つのブランチだけでマージプランを取得するのは難しい場合があります。

  1. インデックスシークが指定された順序を提供することを受け入れるため、ソートは不要ですbefore
  2. マージ操作で使用できるので、TOPの後にソートする必要はありません。
21
Martin Smith

この場合、Unionを実行する必要があることに少し戸惑い、驚いています。以下はシンプルでより効率的です

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 

Execution plan comparison

1
Pushkar Aditya