私はテーブルに次の整数値があるとしましょう
32
11
15
123
55
54
23
43
44
44
56
23
OK、リストは続行できます。関係ありません。ここで、このテーブルにクエリを実行し、特定の数のを返しclosest recordsます。numer 32に最も近いレコードを10個返したいとしましょう。これを効率的に達成できますか?
SQL Server 2014にあります。
私はテーブルに次の整数値があるとしましょう
32
11
15
123
55
54
23
43
44
44
56
23
OK、リストは続行できます。関係ありません。ここで、このテーブルにクエリを実行し、特定の数のを返しclosest recordsます。numer 32に最も近いレコードを10個返したいとしましょう。これを効率的に達成できますか?
SQL Server 2014にあります。
回答:
列にインデックスが付けられていると仮定すると、以下は合理的に効率的です。
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つのブランチだけでマージプランを取得するのは難しい場合があります。
TOP 私はこのケースで連合をしなければならないことに少し困惑し、驚いています。以下はシンプルで効率的です
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 
              
SELECT TOP 10 * FROM YourTable ORDER BY ABS(YourCol - 32) ;さらにシンプルに使用することもできます。効率的でもありません。