CROSS APPLYは外部結合を生成します


17

パーティションで異なるSQLカウントへの回答として、Erik Darlingはこのコードを投稿し、以下の不足を回避しましたCOUNT(DISTINCT) OVER ()

SELECT      *
FROM        #MyTable AS mt
CROSS APPLY (   SELECT COUNT(DISTINCT mt2.Col_B) AS dc
                FROM   #MyTable AS mt2
                WHERE  mt2.Col_A = mt.Col_A
                -- GROUP BY mt2.Col_A 
            ) AS ca;

クエリの使用CROSS APPLY(ないOUTER APPLY)、なぜそこにある外側は代わりの実行計画に参加し、内側が参加?

ここに画像の説明を入力してください

また、group by句のコメントを外すと内部結合が発生するのはなぜですか?

ここに画像の説明を入力してください

データは重要ではないと思いますが、他の質問のkevinwhatによって与えられたデータからコピーします。

create table #MyTable (
Col_A varchar(5),
Col_B int
)

insert into #MyTable values ('A',1)
insert into #MyTable values ('A',1)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',2)
insert into #MyTable values ('A',3)

insert into #MyTable values ('B',4)
insert into #MyTable values ('B',4)
insert into #MyTable values ('B',5)

回答:


23

概要

SQL Serverは正しい結合(内部または外部)を使用し、必要に応じて投影を追加して、applyjoinの間で内部変換を実行するときに元のクエリのすべてのセマンティクス尊重します。

計画の違いはすべて、SQL Serverのgroup by句を使用した場合と使用しない場合の集計の異なるセマンティクスによって説明できます。


詳細

参加vs適用

適用結合を区別できるようにする必要があります

  • 適用する

    内側(下側)の入力がアプライ現在の外側の列によって提供される1つ以上の内側のパラメータ値と、外側(上側)入力の各行に対して実行されます。適用の全体的な結果は、パラメーター化された内側の実行によって生成されたすべての行の組み合わせ(すべてを結合)です。パラメータの存在は、適用が相関結合と呼ばれることを意味します

    アプライ常にによって実行計画に実装されている入れ子になったループ演算子。演算子には、述語の結合ではなく、外部参照プロパティがあります。外部参照は、ループの各反復で外側から内側に渡されるパラメーターです。

  • 参加する

    結合は、結合演算子で結合述語を評価します。結合は通常、SQL ServerのHash MatchMerge、または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と評価されない場合、左結合はNULLfor列を生成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デモ


-3

クロスアプライは、データに対する論理操作です。そのデータを取得する方法を決定するとき、SQL Serverは適切な物理演算子を選択して、必要なデータを取得します。

物理的な適用演算子はありません。SQLServerはそれを適切で、うまくいけば効率的な結合演算子に変換します。

以下のリンクで物理演算子のリストを見つけることができます。

https://docs.microsoft.com/en-us/sql/relational-databases/showplan-logical-and-physical-operators-reference?view=sql-server-2017

クエリオプティマイザーは、論理演算子で構成されるツリーとしてクエリプランを作成します。クエリオプティマイザーがプランを作成した後、クエリオプティマイザーは各論理演算子に対して最も効率的な物理演算子を選択します。クエリオプティマイザーは、コストベースのアプローチを使用して、論理演算子を実装する物理演算子を決定します。

通常、論理演算は複数の物理演算子によって実装できます。ただし、まれに、物理オペレーターが複数の論理操作を実装できる場合があります。

編集/あなたの質問を間違って理解したようです。SQLサーバーは通常、最も適切な演算子を選択します。クエリは、両方のテーブルのすべての組み合わせに対して値を返す必要はありません。これは、クロス結合が使用される場合です。各行に必要な値を計算するだけで、ここで実行できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.