web-dev-qa-db-ja.com

存在しない対存在しない

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

存在しません:

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

479
ilitirit

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

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

Products.ProductID[Order Details].ProductIDNULLsを許可しない場合、NOT INは次のクエリと同様に扱われます。

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

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

Neither 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].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) 

これは、[Order Details]NULLProductIdsが含まれる場合、結果を返さないという正しいセマンティクスのためです。計画に追加されていることを確認するには、追加の反半結合および行カウントスプールを参照してください。

One 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) 

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

Both NULL

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

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

ただし、これはNULL- able列のNOT INの唯一の可能な実行プランではありません。 この記事は別の記事を示しますAdventureWorks2008データベースに対するクエリの場合。

NOT IN列のNOT NULL、またはNULL入力可能またはNULL入力不可の列に対するNOT EXISTSの場合、次の計画を示します。

Not EXists

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

Not In - Null

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

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

650
Martin Smith

また、NOT INは、NULLになるとNOT EXISTSと同じにはなりません。

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

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

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

この理由は、NOT IN操作が実際に何を意味するのかの詳細を調べることによって見つけることができます。

説明のために、テーブル内にtという名前の4行があり、IDという値が1..4の列があるとします。

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)

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

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

74
buckley

実行計画者が同じであると言っても同じです。どちらか一方を使用すると、あなたの意図がより明確になります。この場合は2番目です。

22
John Millikin

実は、これが一番速いと思います。

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

私は約120,000レコードを持つテーブルを持っていて、およそ1500、4000、400、200の行数を持つ他の4つのテーブルの中で存在しないもの(varcharカラムと一致する)だけを選択する必要があります。該当するVarchar列に。

NOT INは約10分かかり、NOT EXISTSは4秒かかりました。

私は10分に貢献したかもしれないいくつかの未調整セクションを持っているかもしれない再帰クエリを持っていますが、NOT EXISTSははるかに優れているか少なくともINEXISTSは正確に同じではないコードを読み進める前に必ずチェックする価値があります。

10
Yella Chalamala

あなたの特定の例では、それらは同じです。オプティマイザはあなたがやろうとしていることが両方の例で同じであることを考え出したからです。しかし、自明ではない例ではオプティマイザがこれを実行しない可能性があります。その場合は、場合によっては他を優先する理由があります。

外側のselectで複数の行をテストしている場合は、NOT INをお勧めします。 NOT INステートメント内で副照会を実行するたびに副選択を再実行するのではなく、NOT EXISTSステートメント内の副照会を実行の開始時に評価し、一時表を外部選択の各値に対して検査することができます。

副照会mustが外部選択と相関している場合、オプティマイザーが同じ機能を実行するための一時表の作成を妨げる単純化を発見する可能性があるため、NOT EXISTSが推奨されます。

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

5
ravish.hacker

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

効率の面では、 left joinがnull ステートメントの方が効率的です(豊富な行を選択する場合)

オプティマイザがそれらが同じであると言ったら、人的要因を検討してください。 NOT EXISTS :)を見るのが好きです。

1
onedaywhen