概要
SQL Serverは正しい結合(内部または外部)を使用し、必要に応じて投影を追加して、applyとjoinの間で内部変換を実行するときに元のクエリのすべてのセマンティクスを尊重します。
計画の違いはすべて、SQL Serverのgroup by句を使用した場合と使用しない場合の集計の異なるセマンティクスによって説明できます。
詳細
参加vs適用
適用と結合を区別できるようにする必要があります。
適用する
内側(下側)の入力がアプライ現在の外側の列によって提供される1つ以上の内側のパラメータ値と、外側(上側)入力の各行に対して実行されます。適用の全体的な結果は、パラメーター化された内側の実行によって生成されたすべての行の組み合わせ(すべてを結合)です。パラメータの存在は、適用が相関結合と呼ばれることを意味します。
アプライ常にによって実行計画に実装されている入れ子になったループ演算子。演算子には、述語の結合ではなく、外部参照プロパティがあります。外部参照は、ループの各反復で外側から内側に渡されるパラメーターです。
参加する
結合は、結合演算子で結合述語を評価します。結合は通常、SQL ServerのHash Match、Merge、またはNested Loops演算子によって実装できます。
ときにネストされたループが選択され、それは区別することができる適用の欠如によって外側リファレンス(及び通常結合述の存在)。結合の内部入力は外部入力からの値を参照しません。内部は外部行ごとに1回実行されますが、内部実行は現在の外部行の値に依存しません。
詳細については、私の投稿の適用とネストされたループの結合を参照してください。
... 内部結合の代わりに実行計画に外部結合があるのはなぜですか?
外部結合は、オプティマイザーが結合への適用を変換するときに発生します(と呼ばれるルールを使用ApplyHandler
)、より安価な結合ベースのプランを見つけることができるかどうかを確認。外であるために参加するために必要とされるジョイン正当とき適用含まスカラ集合を。インナーはされない参加保証元は同じ結果を生成するために適用され、我々が見るように。
スカラーとベクトルの集計
- 対応する
GROUP BY
句のない集約は、スカラー集約です。
- 対応する
GROUP BY
句を含む集合体は、ベクトル集合体です。
SQL Serverでは、集計する行が指定されていない場合でも、スカラー集計は常に行を生成します。例えば、スカラーCOUNT
ない行の集合はゼロです。ベクター COUNT
ない行の集合は空集合(全く行)です。
次のおもちゃのクエリは、違いを示しています。また、スカラーおよびベクトル集合体の楽しみについての私の記事で、スカラーおよびベクトル集合体について詳しく読むこともできます。
-- Produces a single zero value
SELECT COUNT_BIG(*) FROM #MyTable AS MT WHERE 0 = 1;
-- Produces no rows
SELECT COUNT_BIG(*) FROM #MyTable AS MT WHERE 0 = 1 GROUP BY ();
db <> fiddleデモ
参加申請の変換
元の適用に次のものが含まれる場合、結合は正確性のために外部結合である必要があることを前に述べましたスカラー集計。これがなぜそうなのかを詳細に示すために、質問クエリの簡単な例を使用します。
DECLARE @A table (A integer NULL, B integer NULL);
DECLARE @B table (A integer NULL, B integer NULL);
INSERT @A (A, B) VALUES (1, 1);
INSERT @B (A, B) VALUES (2, 2);
SELECT * FROM @A AS A
CROSS APPLY (SELECT c = COUNT_BIG(*) FROM @B AS B WHERE B.A = A.A) AS CA;
はスカラーであるため、列の正しい結果c
はゼロですCOUNT_BIG
集計です。この適用クエリを変換してフォームを結合すると、SQL Serverは、T-SQLで表現された場合に次のように見える内部代替を生成します。
SELECT A.*, c = COALESCE(J1.c, 0)
FROM @A AS A
LEFT JOIN
(
SELECT B.A, c = COUNT_BIG(*)
FROM @B AS B
GROUP BY B.A
) AS J1
ON J1.A = A.A;
適用を無相関結合として書き換えるにはGROUP BY
、派生テーブルにaを導入する必要があります(そうしないA
と、結合する列がなくなる可能性があります)。結合は外部結合である必要があるため、テーブル@A
の各行は出力で行を生成し続けます。結合述部がtrueと評価されない場合、左結合はNULL
for列を生成c
します。NULL
ニーズがでゼロに変換しますCOALESCE
から、正しい変換完了までに適用されますが。
以下のデモは、外部結合と、元の結合をCOALESCE
使用して同じ結果を生成するために必要な方法の両方を示しています適用クエリます。
db <> fiddleデモ
とともに GROUP BY
...なぜgroup by句のコメントを外すと内部結合になりますか?
単純化した例を続けますが、次を追加しGROUP BY
ます。
DECLARE @A table (A integer NULL, B integer NULL);
DECLARE @B table (A integer NULL, B integer NULL);
INSERT @A (A, B) VALUES (1, 1);
INSERT @B (A, B) VALUES (2, 2);
-- Original
SELECT * FROM @A AS A
CROSS APPLY
(SELECT c = COUNT_BIG(*) FROM @B AS B WHERE B.A = A.A GROUP BY B.A) AS CA;
これCOUNT_BIG
はベクトル集合であるため、空の入力セットの正しい結果はゼロではなく、行がまったくありません。つまり、上記のステートメントを実行しても出力は生成されません。
変換するときに、これらのセマンティクスは名誉にはるかに容易で適用することが加わるので、CROSS APPLY
自然に全く内側行を生成しない任意の外側の列を拒否します。したがって、余分な式の射影なしで内部結合を安全に使用できます。
-- Rewrite
SELECT A.*, J1.c
FROM @A AS A
JOIN
(
SELECT B.A, c = COUNT_BIG(*)
FROM @B AS B
GROUP BY B.A
) AS J1
ON J1.A = A.A;
以下のデモは、内部結合の書き換えにより、元のベクトル集約の適用と同じ結果が生成されることを示しています。
db <> fiddleデモ
オプティマイザは、安いテーブルを見つけるため、小さなテーブルとのマージ内部結合を選択します 結合プランをすばやく(十分なプランが見つかる)。コストベースのオプティマイザーは、結合を適用に書き直します-ループ結合または強制シークのヒントが使用される場合、おそらくより安価な適用計画を見つけますが、この場合は努力する価値はありません。
ノート
簡略化された例では、内容が異なるさまざまなテーブルを使用して、意味の違いをより明確に示しています。
オプティマイザーは、不一致(非結合)行を生成できない自己結合について推論できるはずであると主張することができますが、今日ではそのロジックは含まれていません。クエリで同じテーブルに複数回アクセスしても、分離レベルと同時アクティビティに応じて、一般に同じ結果が得られるとは限りません。
オプティマイザはこれらのセマンティクスとエッジケースを心配するので、必要はありません。
ボーナス:内部適用プラン
SQL Server は、クエリの例に対して内部適用計画(内部結合計画ではありません!)を作成できますが、コスト上の理由から選択しないだけです。質問に示されている外部結合プランのコストは0.02898です私のラップトップのSQL Server 2017インスタンスでユニットです。
説明のために、ドキュメント化されていないサポートされていないトレースフラグ9114(無効にするなど)を使用して、適用(相関結合)プランを強制できますApplyHandler
。
SELECT *
FROM #MyTable AS mt
CROSS APPLY
(
SELECT COUNT_BIG(DISTINCT mt2.Col_B) AS dc
FROM #MyTable AS mt2
WHERE mt2.Col_A = mt.Col_A
--GROUP BY mt2.Col_A
) AS ca
OPTION (QUERYTRACEON 9114);
これにより、遅延インデックススプールを使用したネストループの適用計画が作成されます。推定総費用は0.0463983です(選択した計画よりも高い):
ネストされたループの適用を使用する実行計画では、GROUP BY
句の有無に関係なく、「内部結合」セマンティクスを使用して正しい結果が生成されることに注意してください。
現実の世界では、通常、SQL Serverがこのオプションを自然に選択するように、適用の内側でシークをサポートするインデックスがあります。たとえば、次のようになります。
CREATE INDEX i ON #MyTable (Col_A, Col_B);
db <> fiddleデモ