存在しないか存在しない


538

これらのクエリのどれが高速ですか?

存在しません:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE NOT EXISTS (
    SELECT 1 
    FROM Northwind..[Order Details] od 
    WHERE p.ProductId = od.ProductId)

またはない:

SELECT ProductID, ProductName 
FROM Northwind..Products p
WHERE p.ProductID NOT IN (
    SELECT ProductID 
    FROM Northwind..[Order Details])

クエリ実行プランは、両方が同じことを行うと言います。その場合、どちらがお勧めのフォームですか?

これは、NorthWindデータベースに基づいています。

[編集]

この役立つ記事を見つけました:http : //weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

NOT EXISTSに固執すると思います。


3
nullである左結合を使用して計画を試しましたか?
Sebas

1
NOT INとNOT EXISTSは同じではありません。それらの違いについては、このリンクを参照してください:weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
Ameya Gokhale

2
データベースが異なるかどうか疑問に思いますが、PostgreSQLに対する私の最新のベンチマークでは、このNOT INクエリSELECT "A".* FROM "A" WHERE "A"."id" NOT IN (SELECT "B"."Aid" FROM "B" WHERE "B"."Uid" = 2)は次のようにほぼ30倍高速ですNOT EXISTSSELECT "A".* FROM "A" WHERE (NOT (EXISTS (SELECT 1 FROM "B" WHERE "B"."user_id" = 2 AND "B"."Aid" = "A"."id")))
PhươngNguyễnDec


1
@rcdmk質問の日付を確認しましたか?
不正行為者2016年

回答:


693

私は常にデフォルトですNOT EXISTS

実行計画は現時点では同じかもしれませんが、将来的にいずれかの列がNULLs を許可するように変更された場合、NOT INバージョンはより多くの作業を行う必要があります(NULL実際にsがデータに存在しない場合でも)とs 存在するNOT IN場合NULLのセマンティクスとにかく欲しいものになる可能性は低いです。

どちらProducts.ProductID[Order Details].ProductID許可しないか、NULLsを許可するNOT INと、次のクエリと同じように扱われます。

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId) 

正確な計画は異なる場合がありますが、私の例のデータでは、次のようになります。

NULLでもない

合理的によくある誤解は、結合されたサブクエリは結合と比較して常に「悪い」ということです。ネストされたループプラン(行ごとに評価されるサブクエリ)を強制する場合は確かに可能ですが、このプランには反準結合論理演算子が含まれています。反準結合はネストされたループに制限されていませんが、ハッシュ結合またはマージ結合(この例のように)も使用できます。

/*Not valid syntax but better reflects the plan*/ 
SELECT p.ProductID,
       p.ProductName
FROM   Products p
       LEFT ANTI SEMI JOIN [Order Details] od
         ON p.ProductId = od.ProductId 

場合[Order Details].ProductIDであるNULL-ableクエリは、その後になり

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId)
       AND NOT EXISTS (SELECT *
                       FROM   [Order Details]
                       WHERE  ProductId IS NULL) 

これは、sが[Order Details]含まれている場合の正しいセマンティクスがNULL ProductId結果を返さないためです。プランに追加されていることを確認するには、追加の反準結合と行数スプールを参照してください。

1つのNULL

Products.ProductIDNULL-able になるように変更された場合、クエリは

SELECT ProductID,
       ProductName
FROM   Products p
WHERE  NOT EXISTS (SELECT *
                   FROM   [Order Details] od
                   WHERE  p.ProductId = od.ProductId)
       AND NOT EXISTS (SELECT *
                       FROM   [Order Details]
                       WHERE  ProductId IS NULL)
       AND NOT EXISTS (SELECT *
                       FROM   (SELECT TOP 1 *
                               FROM   [Order Details]) S
                       WHERE  p.ProductID IS NULL) 

その理由は、サブクエリが結果をまったく返さなかった場合(つまり、テーブルが空の場合)を除いNULL Products.ProductIdて、結果にa が返されないためです。その場合はそうするべきです。私のサンプルデータの計画では、これは以下のように別の反準結合を追加することで実装されます。NOT IN[Order Details]

どちらもNULL

この影響は、すでにBuckleyによってリンクされているブログ投稿に示されています。この例では、論理読み取りの数が約400から500,000に増加しています。

さらに、シングルNULLが行カウントをゼロに減らすことができるという事実は、カーディナリティの推定を非常に困難にします。SQL Serverがこれが発生すると想定しているが、実際にはNULLデータに行がなかった場合、残りの実行プランは壊滅的に悪化する可能性があります。これが大きなクエリの一部であり、不適切なネストされたループが原因で、高価なサブが繰り返し実行されます。たとえばツリー

これはのための唯一の可能な実行計画ではないNOT INNULLが-able列。この記事ではAdventureWorks2008データベースに対するクエリの別の記事を示します。

以下のためNOT INNOT NULL列またはNOT EXISTSNULL可能か非NULL可能列のいずれかに対しては、次の計画を提供します。

存在しません

列がNULL-ableに変わると、NOT IN計画は次のようになります

ない-Null

プランに内部結合演算子が追加されます。ここではこの装置について説明します。以前の単一の相関インデックスシークをSales.SalesOrderDetail.ProductID = <correlated_product_id>外側の行ごとに2つのシークに変換するのは、これですべてです。追加のものはオンWHERE Sales.SalesOrderDetail.ProductID IS NULLです。

これは反準結合の下にあるため、行が返された場合、2番目のシークは発生しません。ただし、Sales.SalesOrderDetailが含まれていない場合はNULL ProductID、必要なシーク操作の数が2倍になります。


4
示されているようなプロファイリンググラフを取得する方法を聞いてもいいですか。
xis 14

5
@xisこれらは、SQL Sentryプランエクスプローラーで開かれた実行プランです。SSMSで実行計画をグラフィカルに表示することもできます。
マーティンスミス

私はこれを唯一の理由として感謝します:NOT EXISTS私が期待するNOT INように機能します(機能しません)。
levininja

NOT EXISTSでは、データベースが実際にディスクから列を返す必要がないように、NOT EXISTS(SELECT 1 FROM sometable WHERE何か)などのSELECT 1を使用しようとしています。EXPLAINを使用して、これがケースに違いをもたらすかどうかを判断することは、おそらく良い考えです。
Mayur Patel 2015年

4
@Mayur SQL Serverではこれは必要ありません。stackoverflow.com/questions/1597442/...
マーティン・スミス

84

また、NULLになると、NOT INはNOT EXISTSと同等ではないことに注意してください。

この投稿はそれを非常によく説明しています

http://sqlinthewild.co.za/index.php/2010/02/18/not-exists-vs-not-in/

サブクエリがnullを1つでも返す場合、NOT INはどの行とも一致しません。

これの理由は、NOT INオペレーションが実際に何を意味するのか詳細を見ることでわかります。

例として、tというテーブルに4つの行があり、1..4という値を持つIDという列があるとします。

WHERE SomeValue NOT IN (SELECT AVal FROM t)

に相当

WHERE SomeValue != (SELECT AVal FROM t WHERE ID=1)
AND SomeValue != (SELECT AVal FROM t WHERE ID=2)
AND SomeValue != (SELECT AVal FROM t WHERE ID=3)
AND SomeValue != (SELECT AVal FROM t WHERE ID=4)

さらに、ID = 4の場合、AValはNULLであるとしましょう。したがって、!=の比較ではUNKNOWNが返されます。ANDの論理真理値表では、UNKNOWNおよびTRUEはUNKNOWN、UNKNOWN、FALSEはFALSEであると記載されています。結果を生成するためにUNKNOWNとANDで結合できる値はありません

したがって、そのサブクエリのいずれかの行がNULLを返す場合、NOT IN演算子全体がFALSEまたはNULLに評価され、レコードは返されません。


24

実行プランナーが同じであると言った場合、それらは同じです。どちらか一方を使用して、意図をより明確にします。この場合は2番目です。


3
実行プランナーの時間は同じでも、実行結果が異なる可能性があるため、違いがあります。データセットにNULLがある場合、NOT INは予期しない結果を生成します(バックリーの回答を参照)。NOT EXISTSをデフォルトとして使用するのが最適です。
nanonerd 2015年

15

実際、これが最速だと思います:

SELECT ProductID, ProductName 
    FROM Northwind..Products p  
          outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null

2
オプティマイザがその仕事をしているときは最速ではないかもしれませんが、そうでないときは確かに速くなります。
Cade Roux、

2
彼もこの投稿に対するクエリを簡略化した可能性があります
Kip

1
多くの場合、左外部結合はサブクエリよりも高速です。
HLGEM 2008

7
@HLGEM同意しない。私の経験では、LOJの最良のケースはそれらが同じであり、SQL ServerがLOJを反準結合に変換することです。最悪の場合、SQL Server LEFTはすべてを結合し、NULLをフィルターで除外します。その後、非効率になる可能性があります。この記事の最後にある例
マーティン・スミス

12

約120,000レコードのテーブルがあり、行数が約1500、4000、40000、200である他の4つのテーブルに存在しない(varchar列と一致する)テーブルのみを選択する必要があります。関係するすべてのテーブルに一意のインデックスがあります関連するVarchar列に。

NOT IN約10分、NOT EXISTS4秒かかりました。

私は10分に貢献したかもしれないいくつかの未調整のセクションがあるかもしれない再帰クエリがありますが、4秒かかる他のオプションは、少なくとも私に、NOT EXISTSはるかに優れているか少なくともそれINEXISTSあり、まったく同じではなく、常に価値があるコードを進める前に確認してください。


8

特定の例では、オプティマイザが実行しようとしていることが両方の例で同じであることを理解しているため、それらは同じです。しかし、重要な例では、オプティマイザがこれを行わない可能性があり、その場合、場合によってはどちらか一方を優先する理由があります。

NOT IN外部選択で複数の行をテストする場合は、これをお勧めします。NOT INステートメント内のサブクエリは実行の開始時に評価でき、一時テーブルは外部選択の各値に対してチェックできます。NOT EXISTSステートメント。

サブクエリを外部選択と関連付ける必要がある場合はNOT EXISTS、オプティマイザが同じ機能を実行するための一時テーブルの作成を妨げる単純化を発見する可能性があるため、推奨されます。


6

使っていた

SELECT * from TABLE1 WHERE Col1 NOT IN (SELECT Col1 FROM TABLE2)

そしてそれは間違った結果を与えていることがわかりました(間違って私は結果がないことを意味します)。TABLE2.Col1にNULLがあったため。

クエリを次のように変更します

SELECT * from TABLE1 T1 WHERE NOT EXISTS (SELECT Col1 FROM TABLE2 T2 WHERE T1.Col1 = T2.Col2)

正しい結果が得られました。

それ以来、すべての場所でNOT EXISTSを使い始めました。


5

それらは非常に似ていますが、実際には同じではありません。

効率の観点から、左結合はnullステートメントの方が効率的であることがわかりました(豊富な行を選択する場合)。


2

オプティマイザが同じであると言った場合は、人的要因を考慮してください。私は存在しないことを望む:)


1

これは非常に良い質問なので、このトピックについて非常に詳細な記事をブログに書くことにしまし

データベーステーブルモデル

データベースに次の2つのテーブルがあり、1対多のテーブルリレーションシップを形成するとします。

SQL EXISTSテーブル

studentテーブルには、親である、そしてstudent_gradeそれは学生のテーブルのid主キー列を参照STUDENT_ID外部キー列を持っているので、子テーブルです。

student tableは、次の2つのレコードが含まれています。

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

そして、student_gradeテーブルは学生が受け取った成績を保存します:

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

SQLが存在します

数学のクラスで10年生を取得したすべての学生を取得したいとします。

学生IDのみに関心がある場合は、次のようなクエリを実行できます。

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

ただし、アプリケーションはstudent、識別子だけでなくの完全な名前の表示に関心があるため、studentテーブルからの情報も必要です。

studentMathで成績が10であるレコードをフィルタリングするには、次のようにEXISTS SQL演算子を使用できます。

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

上記のクエリを実行すると、Alice行のみが選択されていることがわかります。

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

外部クエリstudentは、クライアントに返す必要がある行列を選択します。ただし、WHERE句は、関連する内部サブクエリでEXISTS演算子を使用しています。

EXISTS演算子は、サブクエリが少なくとも1つのレコードを返す場合はtrueを返し、行が選択されていない場合はfalseを返します。データベースエンジンは、サブクエリを完全に実行する必要はありません。単一のレコードが一致した場合、EXISTS演算子はtrueを返し、関連する他のクエリ行が選択されます。

student_gradeテーブルのstudent_id列が外部のStudentテーブルのid列と照合されるため、内部のサブクエリは相関しています。

SQLが存在しません

9未満の学年を持たないすべての生徒を選択するとします。このために、EXISTS演算子の論理を否定するNOT EXISTSを使用できます。

したがって、基になるサブクエリがレコードを返さない場合、NOT EXISTS演算子はtrueを返します。ただし、単一のレコードが内部サブクエリと一致する場合、NOT EXISTS演算子はfalseを返し、サブクエリの実行を停止できます。

Student_gradeが関連付けられていないすべての生徒のレコードを9未満の値と照合するには、次のSQLクエリを実行します。

SELECT
    id, first_name, last_name
FROM
    student
WHERE NOT EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade < 9
)
ORDER BY id

上記のクエリを実行すると、Aliceレコードのみが一致することがわかります。

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

したがって、SQLのEXISTSおよびNOT EXISTS演算子を使用する利点は、一致するレコードが見つかる限り、内部のサブクエリの実行を停止できることです。


-1

場合によります..

SELECT x.col
FROM big_table x
WHERE x.key IN( SELECT key FROM really_big_table );

キーが含まれているかどうかを確認するクエリチェックのサイズを制限するのにそれほど時間はかかりません。この場合は、EXISTSが推奨されます。

ただし、DBMSのオプティマイザによっては、これも同じです。

EXISTSの方が良い場合の例として

SELECT x.col
FROM big_table x
WHERE EXISTS( SELECT key FROM really_big_table WHERE key = x.key);
  AND id = very_limiting_criteria

1
INEXISTS SQL Serverで同じプラン取得します。問題はとにかくNOT INvs についてNOT EXISTSです。
マーティン・スミス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.