NOT IN vs YOK DEĞİL


538

Bu sorgulardan hangisi daha hızlı?

VAR DEĞİL:

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

Veya DEĞİL:

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

Sorgu yürütme planı her ikisinin de aynı şeyi yaptığını söylüyor. Bu durumda önerilen form hangisidir?

Bu, NorthWind veritabanını temel alır.

[Düzenle]

Bu yararlı makaleyi yeni buldum: http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

Ben var DEĞİL sopa ile düşünüyorum.


3
Soldaki bir birleşmeyi kullanarak planı boş olarak denediniz mi?
Sebas

1
NOT IN ve EXISTS aynı değildir. Aralarındaki fark için bu bağlantıya bir göz atın: weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx
Ameya Gokhale

2
Veritabanlarının farklı olup olmadığını merak ediyorum, ancak PostgreSQL'e karşı en son karşılaştırmamda, bu NOT INsorgu: SELECT "A".* FROM "A" WHERE "A"."id" NOT IN (SELECT "B"."Aid" FROM "B" WHERE "B"."Uid" = 2)neredeyse 30 kat daha hızlı NOT EXISTS:SELECT "A".* FROM "A" WHERE (NOT (EXISTS (SELECT 1 FROM "B" WHERE "B"."user_id" = 2 AND "B"."Aid" = "A"."id")))
Phương Nguyễn


1
@rcdmk Soruların tarihini kontrol ettiniz mi?
ilitirit

Yanıtlar:


693

Ben her zaman varsayılan olarak NOT EXISTS.

Yürütme planları şu anda aynı olabilir ama ya sütun izin vermek için gelecekte değiştirilirse NULLs a NOT IN(hayır bile daha fazla çalışma yapmak gerekir versiyonunu NULLler aslında veri mevcuttur) ve semantik NOT INeğer NULLler vardır mevcut zaten istedikleriniz olma ihtimali düşüktür.

Ne zaman ne Products.ProductIDya [Order Details].ProductIDizin NULLs NOT INaşağıdaki sorguya aynı şekilde muamele edilecektir.

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

Tam plan değişebilir, ancak örnek verilerim için aşağıdakileri elde ederim.

Hiçbiri NULL

Oldukça yaygın bir yanlış anlama, ilişkili alt sorguların birleştirmelere kıyasla her zaman "kötü" olduğu görülmektedir. Kesinlikle iç içe döngüler planını zorladığında olabilir (alt sorgu satır satır değerlendirilir), ancak bu plan bir anti yarı birleştirme mantıksal işleci içerir. Anti yarı birleşimler iç içe döngülerle sınırlı değildir ancak karma veya birleştirme (bu örnekte olduğu gibi) birleşimlerini de kullanabilir.

/*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 

Eğer [Order Details].ProductIDbir NULLsıfat daha sorgu ardından olur

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) 

Bunun nedeni, eğer s [Order Details]içeriyorsa , doğru anlambilimin NULL ProductIdsonuç vermemesidir. Plana eklenen bunu doğrulamak için ekstra anti yarı birleşme ve sıra sayısı makarasına bakın.

Bir NULL

Eğer Products.ProductIDayrıca olmaya değiştirilir NULLsıfat daha sorgu ardından olur

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) 

Bunun nedeni , alt sorgunun hiçbir sonuç döndürmemesi dışındaNULL Products.ProductId sonuçlarda a döndürülmemesidir (yani tablo boştur). Bu durumda olmalı. Örnek verilerimin planında bu, aşağıdaki gibi başka bir anti yarı birleştirme eklenerek uygulanır.NOT IN[Order Details]

İkisi de NULL

Bunun etkisi zaten Buckley tarafından bağlanmış olan blog yazısında gösterilmiştir . Örnekte, mantıksal okumaların sayısı 400'den 500.000'e yükselmiştir.

Ek olarak, tek bir NULLkişinin sıra sayısını sıfıra indirebilmesi, kardinalite tahminini çok zorlaştırır. SQL Server bunun olacağını varsayar, ancak aslında NULLverilerde hiç satır yoksa yürütme planının geri kalanı felaket açısından daha kötü olabilir, bu daha büyük bir sorgunun parçasıysa, uygun olmayan iç içe döngüler pahalı bir alt öğenin tekrar yürütülmesine neden olur ağaç gibi .

Ancak bu, NOT INaçık bir NULLsütun için olası tek yürütme planı değildir . Bu makalede,AdventureWorks2008 veritabanında sorgu için başka bir makale gösterilmektedir .

İçin NOT INbir üzerinde NOT NULLsütun veya NOT EXISTSbir null olan veya olmayan null sütuna ya karşı aşağıdaki planını verir.

Mevcut Değil

Sütun NULL-able olarak değiştiğinde , NOT INplan artık şuna benziyor

Değil - Boş

Plana ekstra bir iç birleştirme operatörü ekler. Bu aygıt burada açıklanmaktadır . Önceki tek korelasyonlu endeks arayışını Sales.SalesOrderDetail.ProductID = <correlated_product_id>dış sıra başına iki aramaya dönüştürmek için her şey var . Ek olan açık WHERE Sales.SalesOrderDetail.ProductID IS NULL.

Bu bir anti-yarı birleşim altında olduğu için, eğer herhangi bir satır döndürürse, ikinci arama gerçekleşmeyecektir. Bununla birlikte Sales.SalesOrderDetail, herhangi bir NULL ProductIDs içermezse , gerekli arama işlemlerinin sayısını iki katına çıkarır.


4
Gösterildiği gibi profil grafiğini nasıl alacağınızı sorabilir miyim?
xis

5
@xis Bunlar, SQL Sentry plan gezgininde açılan yürütme planlarıdır. Yürütme planlarını SSMS'de grafik olarak da görüntüleyebilirsiniz.
Martin Smith

Bunu tek nedeni için takdir ediyorum: NOT EXISTSişlev beklediğim NOT INşekilde çalışır (ki, değil).
levininja

NOT EXISTS ile, veritabanı gerçekten diskten sütunları döndürmek gerekmez böylece NOT EXISTS (bazı yerlerde NEDEN bir şey SEÇ 1) gibi SEÇ 1 kullanmaya çalışın. Bunun sizin durumunuzda bir fark yaratıp yaratmadığını belirlemek için EXPLAIN'i kullanmak muhtemelen iyi bir fikirdir.
Mayur Patel

4
@Mayur SQL Server'da buna gerek yok. stackoverflow.com/questions/1597442/…
Martin Smith

84

Ayrıca null olduğunda NOT IN'in EXISTS ile eşdeğer olmadığını unutmayın.

Bu yazı çok iyi açıklıyor

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

Alt sorgu bir boş bile döndürdüğünde, NOT IN hiçbir satırla eşleşmez.

Bunun nedeni, NOT IN işleminin gerçekte ne anlama geldiğinin ayrıntılarına bakarak bulunabilir.

Diyelim ki, tabloda t adında 4 satır olduğu için, 1 değerlerine sahip ID adında bir sütun var.

WHERE SomeValue NOT IN (SELECT AVal FROM t)

eşittir

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)

Ayrıca, AVal'in ID = 4 olduğu NULL olduğunu söyleyelim. Bu yüzden! = Karşılaştırma BİLİNMİYOR. AND için mantıksal doğruluk tablosu BİLİNMEYEN ve DOĞRU BİLİNMEYEN, BİLİNMEYEN ve YANLIŞ YANLIŞ olduğunu belirtir. DOĞRU sonucunu üretmek için BİLİNMEYEN VE OLAN hiçbir değer yoktur

Bu nedenle, bu alt sorgunun herhangi bir satırı NULL döndürürse, NOT IN işlecinin tamamı FALSE veya NULL olarak değerlendirilir ve hiçbir kayıt döndürülmez


24

İcra planlayıcısı aynı olduklarını söylerse, aynıdır. Niyetinizi daha açık hale getirecek olanı kullanın - bu durumda ikincisi.


3
yürütme planlayıcısı süresi aynı olabilir, ancak yürütme sonuçları farklı olabilir, bu nedenle bir fark vardır. Veri kümenizde NULL varsa NOT IN beklenmedik sonuçlar üretecektir (buckley'nin cevabına bakınız). Varsayılan olarak NOT EXISTS kullanmak en iyisidir.
nanonerd

15

Aslında, bunun en hızlı olacağına inanıyorum:

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

2
Optimize edici işini yaparken en hızlı olmayabilir, ancak kesinlikle daha hızlı olacaktır.
Cade Roux

2
Bu yazı için de sorgusunu basitleştirmiş olabilir
Kip

1
Kabul et Sol dış birleşim genellikle bir alt sorgudan daha hızlıdır.
HLGEM

7
@HLGEM Katılmıyorum. Benim durumumda, LOJ için en iyi durum aynı oldukları ve SQL Server'ın LOJ'u bir anti yarı birleştirmeye dönüştürmesidir. En kötü durumda, SQL Server LEFT her şeyi birleştirir ve NULL'ları filtreler, daha sonra verimsiz olabilir. Bu makalenin altındaki örnek
Martin Smith

12

Yaklaşık 120.000 kayıtları olan ve yalnızca 1500, 4000, 40000, 200 satır sayısı ile diğer dört tabloda var olmayan (bir varchar sütun ile eşleşti) seçmeniz gereken bir tablo var. İlgili tüm tablolar benzersiz bir dizin var ilgili Varcharsütunda.

NOT INyaklaşık 10 dakika NOT EXISTSsürdü, 4 saniye sürdü.

Ben 10 dakika katkıda bulunmuş olabilir bazı ununed bölüm olabilir özyinelemeli bir sorgu var, ama 4 saniye alarak diğer seçenek açıklar, en azından bana NOT EXISTSçok daha iyi veya en azından bu INve EXISTStam olarak aynı ve her zaman değer bir kodla devam etmeden önce kontrol edin.


8

Spesifik örneğinizde bunlar aynıdır, çünkü optimizer her iki örnekte de ne yapmaya çalıştığınızı anladı. Ancak, önemsiz olmayan örneklerde optimize edicinin bunu yapamayabilir ve bu durumda zaman zaman birini diğerini tercih etmek için nedenler olabilir.

NOT INdış seçiminizde birden fazla satır test ediyorsanız tercih edilmelidir. İfadenin içindeki alt sorgu NOT INyürütmenin başlangıcında değerlendirilebilir ve geçici tablo, NOT EXISTSifadeyle her zaman gerektiği gibi alt seçimi yeniden çalıştırmak yerine dış seçimdeki her bir değerle karşılaştırılabilir .

Alt sorgunun dış seçim ile ilişkilendirilmesi gerekiyorsa , o NOT EXISTSzaman tercih edilebilir, çünkü optimize edici aynı işlevi gerçekleştirmek için geçici tabloların oluşturulmasını önleyen bir basitleştirme bulabilir.


6

Kullanıyordum

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

ve bunun yanlış sonuçlar verdiğini buldum (Yanlış demek istemiyorum). TABLE2.Col1 içinde bir NULL olduğu için.

Sorguyu olarak değiştirirken

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

bana doğru sonuçları verdi.

O zamandan beri her yerde NOT EXISTS kullanmaya başladım.


5

Çok benzerler, ama aslında aynı değiller.

Verimlilik açısından, sol birleştirme null deyiminin daha verimli olduğunu gördüm (çok sayıda satır seçildiğinde)


2

Optimize edici aynı olduklarını söylüyorsa, insan faktörünü düşünün. VAR DEĞİL görmeyi tercih ederim :)


1

Bu çok güzel bir soru, bu yüzden blogumda bu konuyla ilgili çok ayrıntılı bir makale yazmaya karar verdim .

Veritabanı tablosu modeli

Diyelim ki veritabanımızda bire çok tablo ilişkisi oluşturan aşağıdaki iki tablo var.

SQL EXISTS tabloları

studentMasa üstüdür ve student_gradeöğrenci tablosundaki id Birincil Anahtar sütununa başvurmak bir student_id Yabancı anahtar sütunu vardır çünkü çocuk tablodur.

student tableAşağıdaki iki kayıtlarını içerir:

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

Ve student_gradetablo öğrencilerin aldığı notları saklar:

| 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 VAR

Matematik dersinde 10 not alan tüm öğrencilere sahip olmak istediğimizi varsayalım.

Yalnızca öğrenci tanımlayıcıyla ilgileniyorsak, bunun gibi bir sorgu çalıştırabiliriz:

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

Ancak, uygulama studentyalnızca tanımlayıcıyı değil, a'nın tam adını görüntülemekle de ilgileniyor , bu yüzden studenttablodan da bilgiye ihtiyacımız var .

studentMath'da 10 notu olan kayıtları filtrelemek için EXISTS SQL işlecini şu şekilde kullanabiliriz:

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

Yukarıdaki sorguyu çalıştırırken yalnızca Alice satırının seçildiğini görebiliriz:

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

Dış sorgu studentistemciye dönmekle ilgilendiğimiz satır sütunlarını seçer . Ancak, WHERE yan tümcesi, EXISTS işlecini ilişkili bir iç alt sorguyla birlikte kullanıyor.

EXISTS işleci, alt sorgu en az bir kayıt döndürürse true, seçili satır yoksa false değerini döndürür. Veritabanı altyapısının alt sorguyu tamamen çalıştırması gerekmez. Tek bir kayıt eşleşirse, EXISTS operatörü true değerini döndürür ve ilişkili diğer sorgu satırı seçilir.

student_gradeTablonun student_id sütunu , dış öğrenci tablosunun id sütunuyla eşleştirildiği için iç alt sorgu ilişkilendirilir .

SQL VAR DEĞİL

Notu 9'dan düşük olmayan tüm öğrencileri seçmek istediğimizi düşünelim. Bunun için EXISTS operatörünün mantığını reddeden NOT EXISTS'i kullanabiliriz.

Bu nedenle, altta yatan alt sorgu kayıt döndürmezse NOT EXISTS işleci true değerini döndürür. Ancak, iç alt sorgu tarafından tek bir kayıt eşleştirilirse, NOT EXISTS operatörü false değerini döndürür ve alt sorgu yürütmesi durdurulabilir.

İlişkili student_grade olmayan tüm öğrenci kayıtlarını 9'dan düşük bir değerle eşleştirmek için aşağıdaki SQL sorgusunu çalıştırabiliriz:

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

Yukarıdaki sorguyu çalıştırırken yalnızca Alice kaydının eşleştiğini görebiliriz:

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

Bu nedenle, SQL EXISTS ve NOT EXISTS işleçlerini kullanmanın avantajı, eşleşen bir kayıt bulunduğu sürece iç alt sorgu yürütmenin durdurulabilmesidir.


-1

Değişir..

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

göreceli olarak yavaş olmayacaktır, sorgu anahtarının içeri girip girmediğini görmek için denetimin boyutunu sınırlamak için fazla değildir. Bu durumda EXISTS tercih edilir.

Ancak, DBMS'nin optimize edicisine bağlı olarak, bu farklı olmayabilir.

EXISTS'in ne zaman daha iyi olduğuna bir örnek olarak

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
INve EXISTS SQL Server'da aynı planı alın . Soru zaten NOT INvs ile ilgili NOT EXISTS.
Martin Smith
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.