585

これらのクエリのうち、どれがより高速ですか?

存在しません:

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 に固執すると思います。

4

11 に答える 11

746

私は常にデフォルトにしていますNOT EXISTS

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

どちらProducts.ProductID[Order Details].ProductID許可NULLしない場合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

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.ProductIDも可能になるように変更された場合NULL、クエリは次のようになります

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 INただし、 on a NULL-able 列の実行計画はこれだけではありません。この記事では、データベースに対するクエリの別の記事を示しAdventureWorks2008ます。

列の場合、またはnull可能またはnull不可の列の場合、次の計画が得られますNOT INNOT NULLNOT EXISTS

存在しません

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

入っていない - Null

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

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

于 2012-06-17T20:10:17.887 に答える
95

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

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

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

サブクエリが 1 つでも null を返す場合、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 演算して結果を TRUE にすることができる値はありません。

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

于 2012-05-09T12:23:38.247 に答える
25

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

于 2008-10-06T02:21:46.137 に答える
18

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

SELECT ProductID, ProductName 
    FROM Northwind..Products p  
          outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null
于 2008-10-06T02:40:33.120 に答える
12

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

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

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

于 2014-07-07T17:12:53.127 に答える
8

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

NOT IN外部選択で複数の行をテストしている場合は、優先する必要があります。ステートメント内のサブクエリNOT INは実行の開始時に評価でき、ステートメントで必要になるたびにサブセレクトを再実行するのではなく、外側のセレクトの各値に対して一時テーブルをチェックできますNOT EXISTS

副問合せを外部選択と関連付ける必要NOT EXISTSがある場合は、オプティマイザが一時表の作成を妨げる簡素化を検出して同じ機能を実行できるため、この方法が望ましい場合があります。

于 2008-10-06T02:54:46.930 に答える
7

使っていました

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 を使い始めました。

于 2013-06-13T15:02:31.620 に答える
5

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

効率の面では、左の結合がnullステートメントであることがより効率的であることがわかりました(大量の行が選択される場合)

于 2018-03-19T08:27:30.717 に答える
2

オプティマイザーがそれらが同じであると言う場合は、人的要因を考慮してください。私はNOT EXISTSを見たいです:)

于 2008-10-06T07:57:08.677 に答える
-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
于 2008-10-06T02:32:34.087 に答える