By: Koen Verbeeck|更新日時:2019-05-13 2019-05-13|コメント(6)|関連するもの その他 > T-SQL
無料のMSSQLTips Webinarです。
このウェビナーでは、SQL Server の開発のベスト プラクティスについて説明します。
問題
特定の値をフィルタリングするために、WHERE 句で T-SQL の IN 演算子または EXISTS 演算子を使用することに違いはありますか? 論理的な違いなのか、パフォーマンスの違いなのか、それともまったく同じなのでしょうか。 また、NOT INやNOT EXISTSについてはどうでしょうか?
解決策
このヒントでは、EXISTS 演算子と IN 演算子の間に違いがあるかどうかを調べます。 これは、論理的なもの、つまり特定の状況下で異なる動作をするもの、またはパフォーマンス的なもの、つまり一方の演算子を使用することで他方の演算子よりもパフォーマンス上の利点があるかどうかを意味します。 テストクエリにはAdventureWorks DW 2017を使用します。
SQL Server IN vs EXISTS
IN演算子は通常、特定の値のリストに対して列をフィルタリングするために使用されます。
SELECT , ,FROM ..WHERE IN (1,2);
このクエリは、productcategories Bikes and Categories (ProductCategoryKey 1 and 2)に属するすべての製品サブカテゴリーを検索します。
サブクエリの結果セット内の値を検索するのに、IN演算子を使用することもできます。
SELECT , ,FROM ..WHERE IN ( SELECT FROM . WHERE = 'Bikes' );
このクエリは、「Bikes」カテゴリーにリンクされたすべてのサブカテゴリーを返します。
サブクエリを使用することの利点は、クエリがハードコードされにくくなることです。 IN演算子が機能するためには、副問い合わせが正確に1つの列を返すことが重要です。
EXISTS演算子は、値をチェックするのではなく、行の存在をチェックします。 通常、副問い合わせはEXISTSと一緒に使用されます。
このクエリは ProductSubcategory テーブルのすべての行を返します。なぜなら、最初のサブクエリは行を返すからです(外側のクエリとはまったく関係ありません)。
SELECT , ,FROM ..WHERE EXISTS ( SELECT 1/0 FROM . WHERE = 'Bikes' );
お気づきのように、サブクエリの SELECT 句には 1/0 があります。 通常のクエリでは、これはゼロ除算のエラーを返しますが、EXISTS句の中では、この除算は計算されないため、全く問題ありません。
EXISTSをより意味のある方法で使用するには、相関副問い合わせを使用することができます。相関副問い合わせでは、外側の問い合わせからの値と、内側(副)問い合わせからの値をペアにします。 これにより、外側の問い合わせの値が内側の問い合わせで使用されるテーブルに存在するかどうかが効果的にチェックされます。 例えば、売上を上げたすべての従業員のリストを返したい場合、次のようなクエリを書くことができます。
SELECT , , ,FROM .. eWHERE EXISTS ( SELECT 1 FROM dbo. f WHERE e. = f. );
EXISTS サブクエリ内の WHERE 句では、外側のテーブルである DimEmployee の従業員キーと、内側のテーブルである FactResellerSales の従業員キーを関連付けています。 両方のテーブルに従業員キーが存在する場合、行が返され、EXISTSはtrueを返します。 従業員キーが FactResellerSales に存在しない場合、EXISTS は false を返し、その従業員は結果から除外されます。
IN演算子を使って同じロジックを実装することができます。
SELECT , , ,FROM .. eWHERE IN ( SELECT FROM dbo. f );
どちらのクエリも同じ結果セットを返しますが、根本的なパフォーマンスの違いがあるのかもしれません。
これはEXISTSのプランです。
これはINのプランです。
見た目はまったく同じです。 両方のクエリを同時に実行すると、同じコストが割り当てられているのがわかります。
上の実行プランは EXISTS のもので、下の実行プランは IN のものです。
IO 統計を見てみましょう (SET STATISTICS IO ON というステートメントを実行することで表示できます)。
このように、証明できるようなパフォーマンスの違いはなく、どちらも同じ結果セットを返しています。 では、どのような場合にどちらか一方を使用するのでしょうか。 以下にガイドラインを示します。
- 静的な値の小さなリストがある場合 (そして、その値が何らかのテーブルに存在しない場合)、IN 演算子を使用することをお勧めします。
- 別のテーブルに値が存在するかどうかをチェックする必要がある場合は、クエリの意図を明確に示すことができる EXISTS 演算子が好ましいです。
- 複数の単一の列に対してチェックする必要がある場合は、IN 演算子では単一の列しかチェックできないため、通常は EXISTS を使用します。 AdventureWorksのデータウェアハウスには、Employeeディメンションがあります。
さて、営業担当者が他のテリトリーでも販売を行う可能性があります。たとえば、北東地域を担当するMichael Blytheは、4つの異なる地域で販売を行っています。
ここで、販売地域のマネージャーの売上金額を、自分の地域だけで調べたいとします。
SELECT f. ,f. ,SUM()FROM . fWHERE EXISTS ( SELECT 1 FROM . e WHERE f. = e. AND f. = e. AND e. <> 11 -- the NA region )GROUP BY f. ,f.;
結果は次のようになります。
EXISTS句の中で、NA地域にリンクされたすべての従業員をフィルタリングして、販売地域のマネージャーを取得します。 外側のクエリでは、内側のクエリで従業員と地域が見つかった場合に、販売地域と従業員ごとのすべての売上を取得します。 このように、EXISTSを使用すると、INではできない複数の列のチェックを簡単に行うことができます。
SQL Server NOT IN vs NOT EXISTS
演算子の前にNOT演算子を付けることで、その演算子のブール出力を否定します。 例えば、NOT INを使用すると、リストで見つからない値を持つすべての行を返します。
ただし、NULL値が登場する場合は特別なケースがあります。 リストにNULL値が含まれている場合、結果セットは空になります。
つまり、副問い合わせの結果セットに突然NULL値が現れた場合、NOT INは予想外の結果を返す可能性があります。 NOT EXISTS では、何が返されるかは問題ではないため、この問題はありません。 空の結果セットが返された場合、NOT EXISTSはこれを否定し、現在のレコードがフィルタリングされないことを意味します。
上記のクエリは、販売を行っていないすべての従業員を返します。 論理的には、NOT IN と NOT EXISTS は同じです。つまり、NULLS が含まれていない限り、同じ結果セットを返すことになります。 パフォーマンスに違いはありますか?
IO 統計についても同様です。
ただし、1つ問題があります。 先に示したように、NOT INはNULLが関係する場合に問題があります。 もしEmployeeKeyをnullableに変更すると、以下のような実行計画になります。
今回はかなりの違いがあります。 SQL ServerがNULL値を考慮しなければならなくなったため、実行計画が変わりました。
これで、NOT IN と NOT EXISTS の間に実際のパフォーマンスの違いが出てきました。 いくつかのガイドラインがあります。
- IN および EXISTS と同様のガイドラインを適用できます。 小さな静的リストに対するチェックでは、NOT INが好ましい。 別のテーブルに存在するかどうかをチェックする場合 NOT EXISTSの方が良いでしょう。 複数の列に対してチェックする場合は、やはりNOT EXISTSです。
- カラムの 1 つが nullable である場合、NOT EXISTS が望ましいです。
UUsing Joins Instead of IN or EXISTS
同じロジックをジョインでも実装できます。 INやEXISTSの代わりには、INNER JOINがあり、NOT INやNOT EXISTSの代わりには、NULL値をチェックするWHERE句を持つLEFT OUTER JOINが使用できます。 これらが全く同じ結果セットと実行計画を返すにもかかわらず、このヒントに含まれていないのは、その意図が異なるからです。 INとEXISTSでは、別のレコード・セットに値が存在するかどうかをチェックします。 結合では、結果セットをマージし、他のテーブルのすべての列にアクセスすることになります。 存在を確認することは、どちらかというと「副作用」にあたります。 (NOT) INや(NOT) EXISTSを使用すると、クエリの意図が明確になります。 一方、Joinには複数の目的があります。
INNER JOINを使用すると、2番目のテーブルに複数のマッチがある場合、同じ値に対して複数の行を返すこともできます。
次のステップ
- このオーバービューでは、さらに多くのT-SQLのヒントを見つけることができます。
- 長年のMVPであるGail Shaw氏は、EXISTS vs IN vs JOINSに関する素晴らしいシリーズを発表しています。 もしあなたがEXISTS/INとJOINSの比較に興味があるなら、次のブログ記事を読むことができます:
- IN vs INNER JOIN
- LEFT OUTER JOIN vs NOT EXISTS
- SQL Server Join Tips
- Tip:SQL Server Join Example
最終更新日:2019-05-05 17:00 2019-05-13
著者について
Koen Verbeeck は BI の専門家です。 Microsoft BIスタックを専門とし、特にSSISを愛しています。
全てのTipsを見る関連リソース- More Database Developer Tips…