数十行のテーブルがあります。簡略化されたセットアップは次のとおりです
CREATE TABLE #data ([Id] int, [Status] int);
INSERT INTO #data
VALUES (100, 1), (101, 2), (102, 3), (103, 2);
そして、私はこのテーブルを一連のテーブル値で構成された行(変数と定数で構成される)に結合するクエリを持っています。
DECLARE @id1 int = 101, @id2 int = 105;
SELECT
COALESCE(p.[Code], 'X') AS [Code],
COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
(@id1, 'A'),
(@id2, 'B')
) p([Id], [Code])
FULL JOIN #data d ON d.[Id] = p.[Id];
クエリ実行プランは、オプティマイザの決定がFULL LOOP JOIN
戦略を使用することであることを示しています。これは、両方の入力に行がほとんどないため、適切と思われます。しかし、私が気付いた(同意できない)ことの1つは、TVC行がスプールされていることです(赤いボックスの実行プランの領域を参照)。
オプティマイザがここにスプールを導入する理由、それを行う理由は何ですか?スプール以外に複雑なものはありません。必要ないようです。この場合それを取り除く方法、可能な方法は何ですか?
上記の計画は
Microsoft SQL Server 2014(SP2-CU11)(KB4077063)-12.0.5579.0(X64)
オプティマイザがここにスプールを導入する理由、それを行う理由は何ですか?スプール以外に複雑なものはありません。
スプールを超えたものは単純なテーブル参照ではなく、左結合/半結合 alternative が生成されるときに単に複製される可能性があります。
これはlookテーブル(定数スキャン)に少し似ていますが、オプティマイザ*にとっては、VALUES
の個別の行のUNION ALL
です。句。
追加の複雑さは、オプティマイザがソース行をスプールして再生することを選択するのに十分であり、スプールを後で単純な「テーブル取得」で置き換えることはありません。たとえば、完全結合からの初期変換は次のようになります。
一般的な変換によって導入された追加のスプールに注意してください。単純なテーブルgetの上のスプールは、ルールSpoolGetToGet
によって後でクリーンアップされます。
オプティマイザに対応するSpoolConstGetToConstGet
ルールがあった場合、原則的には希望どおりに機能します。
この場合それを取り除く方法、可能な方法は何ですか?
実際のテーブル(一時または変数)を使用するか、手動で完全結合からの変換を記述します。次に例を示します。
WITH
p([Id], [Code]) AS
(
SELECT @id1, 'A'
UNION ALL
SELECT @id2, 'B'
),
FullJoin AS
(
SELECT
p.Code,
d.[Status]
FROM p
LEFT JOIN #data d
ON d.[Id] = p.[Id]
UNION ALL
SELECT
NULL,
D.[Status]
FROM #data AS D
WHERE NOT EXISTS
(
SELECT *
FROM p
WHERE p.Id = D.Id
)
)
SELECT
COALESCE(FullJoin.Code, 'X') AS Code,
COALESCE(FullJoin.Status, 0) AS [Status]
FROM FullJoin;
手動書き換えの計画:
これは、元の0.0203412ユニットと比較して、0.0067201ユニットの推定コストです。
*Converted TreeでLogOp_UnionAll
として観察できます(TF 8605)。 入力ツリー(TF 8606)では、LogOp_ConstTableGet
です。 Converted Treeは、解析、正規化、代数化、バインディング、およびその他の準備作業の後のオプティマイザー式要素のツリーを示します。 入力ツリーは、否定正規形への変換(NNF変換)、ランタイム定数の縮小、およびその他のいくつかのビットとボブの後の要素を示しています。 NNF変換には、とりわけ、論理和集合と共通テーブル取得を折りたたむロジックが含まれています。
テーブルスプールは、VALUES
句にある2つのタプルセットからテーブルを作成するだけです。
次のように、これらの値を一時テーブルに最初に挿入することで、スプールを削除できます。
DROP TABLE IF EXISTS #data;
CREATE TABLE #data ([Id] int, [Status] int);
INSERT INTO #data
VALUES (100, 1), (101, 2), (102, 3), (103, 2);
DROP TABLE IF EXISTS #p;
CREATE TABLE #p
(
Id int NOT NULL
, Code char(1) NOT NULL
);
DECLARE @id1 int = 101, @id2 int = 105;
INSERT INTO #p (Id, Code)
VALUES
(@id1, 'A'),
(@id2, 'B');
SELECT
COALESCE(p.[Code], 'X') AS [Code],
COALESCE(d.[Status], 0) AS [Status]
FROM #p p
FULL JOIN #data d ON d.[Id] = p.[Id];
クエリの実行プランを見ると、出力リストにUnion
プレフィックスを使用する2つの列が含まれていることがわかります。これは、スプールがunionされたソースからテーブルを作成しているというヒントです。
FULL OUTER JOIN
では、SQL Serverがp
の値に2回アクセスする必要があります。1回は結合の「サイド」ごとに1回です。スプールを作成すると、結果の内部ループ結合がスプールされたデータにアクセスできるようになります。
興味深いことに、FULL OUTER JOIN
をLEFT JOIN
とRIGHT JOIN
に置き換え、結果をUNION
に置き換えると、SQL Serverはスプールを使用しません。
SELECT
COALESCE(p.[Code], 'X') AS [Code],
COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
(101, 'A'),
(105, 'B')
) p([Id], [Code])
LEFT JOIN #data d ON d.[Id] = p.[Id]
UNION
SELECT
COALESCE(p.[Code], 'X') AS [Code],
COALESCE(d.[Status], 0) AS [Status]
FROM (VALUES
(101, 'A'),
(105, 'B')
) p([Id], [Code])
RIGHT JOIN #data d ON d.[Id] = p.[Id];
上記のUNION
クエリの使用はお勧めしません。入力のセットが大きい場合は、すでに持っている単純なFULL OUTER JOIN
より効率的ではない場合があります。