2つの結果セットをUNIONすることが、JOIN句でORを使用するよりも優れている理由である理由を示すために、サンプルのクエリプランを作成しようとしています。作成したクエリプランでは、困惑しています。 Users.Reputationの非クラスター化インデックスを含むStackOverflowデータベースを使用しています。
CREATE NONCLUSTERED INDEX IX_NC_REPUTATION ON dbo.USERS(Reputation)
SELECT DISTINCT Users.Id
FROM dbo.Users
INNER JOIN dbo.Posts
ON Users.Id = Posts.OwnerUserId
OR Users.Id = Posts.LastEditorUserId
WHERE Users.Reputation = 5
クエリプランは https://www.brentozar.com/pastetheplan/?id=BkpZU1MZE にあり、クエリの実行時間は4:37分、26612行が返されます。
このスタイルの定数スキャンが既存のテーブルから作成されるのを見たことはありません。ユーザーが入力した単一の行に定数スキャンが通常使用されるのに、すべての行に対して一定のスキャンが実行される理由はよくわかりません。たとえば、SELECT GETDATE()です。なぜここで使用されるのですか?このクエリプランを読む際のガイダンスを本当に感謝します。
そのOR=をUNIONに分割すると、同じ26612行が返される12秒で実行される標準計画が作成されます。
SELECT Users.Id
FROM dbo.Users
INNER JOIN dbo.Posts
ON Users.Id = Posts.OwnerUserId
WHERE Users.Reputation = 5
UNION
SELECT Users.Id
FROM dbo.Users
INNER JOIN dbo.Posts
ON Users.Id = Posts.LastEditorUserId
WHERE Users.Reputation = 5
私はこの計画をこれを行うと解釈します:
計画は、私が行ったものと似ています 詳細はこちら 。
Posts
テーブルがスキャンされます。
各行について、OwnerUserId
およびLastEditorUserId
を抽出します。これは、UNPIVOT
の動作と同様です。以下のプランでは、単一の定数スキャン演算子が表示され、各入力行に2つの出力行が作成されます。
SELECT *
FROM dbo.Posts
UNPIVOT (X FOR U IN (OwnerUserId,LastEditorUserId)) Unpvt
この場合、or
のセマンティクスが2つの列の値が同じである場合、Users
の結合から1行のみが出力される必要があるため(2つではなく)、計画は少し複雑になります。
次に、これらはマージ間隔を通過するため、値が同じである場合、範囲は縮小され、Users
に対して1つのシークのみが実行されます。それ以外の場合は、2つのシークが実行されます。
値62
は、シークが等価シークであることを意味するフラグです。
について
IX_NC_REPUTATIONからExpr1010およびExpr1011を含むサブツリーへのネストされたループ結合を実行していない場合、これらにアクセスする方法がわかりません
これらは、黄色で強調表示された連結演算子で定義されています。これは、黄色で強調表示されたネストされたループの外側にあります。したがって、これは、ネストされたループの内側で黄色で強調表示されたシークの前に実行されます。
これが役立つ場合に備えて、同様の計画を与える書き換え(マージ間隔はマージユニオンに置き換えられています)を以下に示します。
SELECT DISTINCT D2.UserId
FROM dbo.Posts p
CROSS APPLY (SELECT Users.Id AS UserId
FROM (SELECT p.OwnerUserId
UNION /*collapse duplicate to single row*/
SELECT p.LastEditorUserId) D1(UserId)
JOIN Users
ON Users.Id = D1.UserId) D2
OPTION (FORCE ORDER)
Posts
テーブルで使用できるインデックスに応じて、このクエリのバリアントは、提案されたUNION ALL
ソリューションよりも効率的です。 (私が持っているデータベースのコピーにはこれに役立つインデックスがなく、提案されたソリューションはPosts
の2つのフルスキャンを実行します。以下は1つのスキャンで実行します)
WITH Unpivoted AS
(
SELECT UserId
FROM dbo.Posts
UNPIVOT (UserId FOR U IN (OwnerUserId,LastEditorUserId)) Unpivoted
)
SELECT DISTINCT Users.Id
FROM dbo.Users INNER HASH JOIN Unpivoted
ON Users.Id = Unpivoted.UserId
WHERE Users.Reputation = 5