私はテーブルに次の整数値があるとしましょう
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) ;
さらにシンプルに使用することもできます。効率的でもありません。