TIDAK DALAM vs TIDAK ADA

538

Manakah dari pertanyaan ini yang lebih cepat?

TIDAK ADA:

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

Atau TIDAK DI:

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

Rencana eksekusi permintaan mengatakan mereka berdua melakukan hal yang sama. Jika demikian, formulir mana yang disarankan?

Ini didasarkan pada database NorthWind.

[Sunting]

Baru saja menemukan artikel yang bermanfaat ini: http://weblogs.sqlteam.com/mladenp/archive/2007/05/18/60210.aspx

Saya pikir saya akan tetap dengan TIDAK ADA.

ilitirit
sumber
3
apakah Anda mencoba rencana dengan menggunakan gabungan kiri di mana nol?
Sebas
1
TIDAK DALAM dan TIDAK ADA tidak identik. Lihatlah tautan ini untuk mengetahui
Ameya Gokhale
2
Saya ingin tahu apakah Database berbeda, tetapi dalam tolok ukur terbaru saya terhadap PostgreSQL, NOT INpertanyaan ini : SELECT "A".* FROM "A" WHERE "A"."id" NOT IN (SELECT "B"."Aid" FROM "B" WHERE "B"."Uid" = 2)hampir 30 kali lebih cepat dari ini NOT EXISTS:SELECT "A".* FROM "A" WHERE (NOT (EXISTS (SELECT 1 FROM "B" WHERE "B"."user_id" = 2 AND "B"."Aid" = "A"."id")))
Phunơng Nguyễn
1
@rcdmk Apakah Anda memeriksa tanggal pada pertanyaan?
ilitirit

Jawaban:

693

Saya selalu default ke NOT EXISTS.

Rencana eksekusi mungkin sama pada saat ini tetapi jika kolom baik diubah di masa depan untuk memungkinkan NULLs NOT INversi akan perlu melakukan lebih banyak pekerjaan (bahkan jika tidak ada NULLs benar-benar hadir dalam data) dan semantik NOT INjika NULLs adalah hadir tidak mungkin menjadi yang Anda inginkan.

Ketika tidak ada Products.ProductIDatau [Order Details].ProductIDmengizinkan NULL, maka NOT INakan diperlakukan secara identik dengan permintaan berikut.

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

Paket persis dapat bervariasi tetapi untuk data contoh saya, saya mendapatkan yang berikut.

Baik NULL

Kesalahpahaman yang cukup umum tampaknya adalah bahwa sub kueri yang berkorelasi selalu "buruk" dibandingkan dengan bergabung. Mereka pasti bisa ketika mereka memaksa rencana loop bersarang (sub-query dievaluasi baris demi baris) tetapi rencana ini termasuk operator anti gabung logis. Anti semi joins tidak terbatas pada loop bersarang tetapi dapat menggunakan hash atau gabungan (seperti dalam contoh ini) bergabung juga.

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

Jika [Order Details].ProductIDadalah NULL-Mampu query kemudian menjadi

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) 

Alasan untuk ini adalah bahwa semantik yang benar jika [Order Details]berisi NULL ProductIds adalah untuk mengembalikan hasil. Lihat spool anti semi join tambahan dan baris untuk memverifikasi ini yang ditambahkan ke paket.

Satu NULL

Jika Products.ProductIDjuga diubah menjadi NULL-able maka pertanyaannya menjadi

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) 

Alasan untuk itu adalah karena a NULL Products.ProductIdtidak boleh dikembalikan dalam hasil kecuali jika NOT INpermintaan sub untuk mengembalikan hasil sama sekali (yaitu [Order Details]tabel kosong). Dalam hal ini seharusnya. Dalam paket data sampel saya ini diterapkan dengan menambahkan anti semi join seperti di bawah ini.

Keduanya NULL

Efek dari ini ditunjukkan dalam posting blog yang sudah ditautkan oleh Buckley . Dalam contoh di sana jumlah bacaan logis meningkat dari sekitar 400 hingga 500.000.

Selain itu fakta bahwa satu NULLdapat mengurangi jumlah baris menjadi nol membuat estimasi kardinalitas sangat sulit. Jika SQL Server mengasumsikan bahwa ini akan terjadi tetapi pada kenyataannya tidak ada NULLbaris dalam data, sisa dari rencana eksekusi mungkin lebih buruk, jika ini hanya bagian dari permintaan yang lebih besar, dengan loop bersarang yang tidak tepat menyebabkan eksekusi berulang sub yang mahal pohon misalnya .

Ini bukan satu-satunya rencana eksekusi mungkin untuk NOT INpada NULLkolom -Mampu namun. Artikel ini menunjukkan satu lagi untuk kueri terhadap AdventureWorks2008basis data.

Untuk NOT INpada NOT NULLkolom atau kolom yang NOT EXISTStidak dapat dibatalkan atau tidak dapat dibatalkan, kolom ini memberikan rencana berikut.

Tidak ada

Ketika kolom berubah menjadi NULL-able, NOT INrencana sekarang terlihat seperti

Tidak Di - Null

Ini menambah operator join batin ekstra ke paket. Alat ini dijelaskan di sini . Itu semua ada untuk mengubah indeks berkorelasi tunggal sebelumnya mencari Sales.SalesOrderDetail.ProductID = <correlated_product_id>dua mencari per baris luar. Yang tambahan aktif WHERE Sales.SalesOrderDetail.ProductID IS NULL.

Karena ini berada di bawah anti semi join jika yang mengembalikan setiap baris pencarian kedua tidak akan terjadi. Namun jika Sales.SalesOrderDetailtidak mengandung apa pun NULL ProductID, itu akan menggandakan jumlah operasi pencarian yang diperlukan.

Martin Smith
sumber
4
Bolehkah saya bertanya bagaimana Anda mendapatkan grafik profil seperti yang ditunjukkan?
xis
5
@xis Ini adalah paket eksekusi yang dibuka di dalam SQL Sentry plan explorer. Anda juga dapat melihat rencana eksekusi secara grafis di SSMS.
Martin Smith
Saya menghargai ini karena satu-satunya alasan bahwa: NOT EXISTSberfungsi seperti yang saya harapkan NOT INberfungsi (yang, tidak).
levininja
Dengan NOT EXISTS, saya mencoba menggunakan SELECT 1 seperti NOT EXISTS (SELECT 1 FROM sesuatu WHERE sesuatu) sehingga database sebenarnya tidak perlu mengembalikan kolom dari disk. Menggunakan EXPLAIN untuk menentukan apakah ini membuat perbedaan dalam kasus Anda mungkin merupakan ide yang bagus.
Mayur Patel
4
@ Mayur Tidak perlu untuk ini di SQL Server. stackoverflow.com/questions/1597442/…
Martin Smith
84

Perlu diketahui juga bahwa NOT IN tidak setara dengan NOT EXISTS ketika datang ke null.

Posting ini menjelaskannya dengan sangat baik

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

Ketika subquery mengembalikan satu nol, NOT IN tidak akan cocok dengan baris mana pun.

Alasan untuk ini dapat ditemukan dengan melihat rincian dari arti sebenarnya dari operasi NOT IN.

Katakanlah, untuk tujuan ilustrasi bahwa ada 4 baris dalam tabel yang disebut t, ada kolom yang disebut ID dengan nilai 1..4

WHERE SomeValue NOT IN (SELECT AVal FROM t)

setara dengan

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)

Mari kita katakan bahwa AVal adalah NULL di mana ID = 4. Oleh karena itu! = Perbandingan mengembalikan TIDAK DIKETAHUI. Tabel kebenaran logis untuk DAN menyatakan bahwa TIDAK DIKETAHUI dan BENAR adalah TIDAK DIKETAHUI, TIDAK DIKETAHUI, dan SALAH adalah SALAH. Tidak ada nilai yang dapat DAN akan dengan UNKNOWN untuk menghasilkan hasil yang BENAR

Oleh karena itu, jika setiap baris subquery mengembalikan NULL, seluruh operator NOT IN akan mengevaluasi FALSE atau NULL dan tidak ada catatan yang akan dikembalikan

buckley
sumber
24

Jika perencana eksekusi mengatakan mereka sama, mereka sama. Gunakan mana saja yang akan membuat niat Anda lebih jelas - dalam hal ini, yang kedua.

John Millikin
sumber
3
waktu perencana pelaksanaan mungkin sama tetapi hasil eksekusi dapat berbeda sehingga ada perbedaan. NOT IN akan menghasilkan hasil yang tidak terduga jika Anda memiliki NULL dalam dataset Anda (lihat jawaban buckley). Terbaik untuk menggunakan BUKAN ADA sebagai default.
nanonerd
15

Sebenarnya, saya percaya ini akan menjadi yang tercepat:

SELECT ProductID, ProductName 
    FROM Northwind..Products p  
          outer join Northwind..[Order Details] od on p.ProductId = od.ProductId)
WHERE od.ProductId is null
James Curran
sumber
2
Mungkin bukan yang tercepat ketika optimizer melakukan tugasnya, tetapi tentu saja akan lebih cepat ketika tidak.
Cade Roux
2
Dia mungkin telah menyederhanakan kueri untuk posting ini juga
Kip
1
Setuju Gabung luar kiri seringkali lebih cepat daripada subquery.
HLGEM
7
@HLGEM Tidak Setuju. Dalam pengalaman saya kasus terbaik untuk LOJ adalah bahwa mereka sama dan SQL Server mengubah LOJ menjadi anti bergabung. Dalam kasus terburuk SQL Server LEFT BERGABUNG segalanya dan menyaring NULL setelah yang bisa jauh lebih tidak efisien. Contohnya di bagian bawah artikel ini
Martin Smith
12

Saya memiliki tabel yang memiliki sekitar 120.000 catatan dan harus memilih hanya yang tidak ada (cocok dengan kolom varchar) di empat tabel lainnya dengan jumlah baris sekitar 1500, 4000, 40000, 200. Semua tabel yang terlibat memiliki indeks unik di Varcharkolom yang bersangkutan .

NOT INbutuh sekitar 10 menit, NOT EXISTSbutuh 4 detik.

Saya memiliki kueri rekursif yang mungkin memiliki beberapa bagian tanpa tanda yang mungkin telah berkontribusi pada 10 menit, tetapi opsi lain mengambil 4 detik menjelaskan, NOT EXISTSsetidaknya bagi saya itu jauh lebih baik atau setidaknya itu INdan EXISTStidak persis sama dan selalu bernilai periksa sebelum melanjutkan dengan kode.

Yella Chalamala
sumber
8

Dalam contoh spesifik Anda, keduanya sama, karena pengoptimal telah mengetahui apa yang Anda coba lakukan adalah sama pada kedua contoh. Tetapi ada kemungkinan bahwa dalam contoh non-sepele pengoptimal mungkin tidak melakukan ini, dan dalam hal ini ada alasan untuk lebih memilih satu dari yang lain pada kesempatan.

NOT INharus lebih disukai jika Anda menguji beberapa baris dalam pemilihan luar Anda. Subquery di dalam NOT INpernyataan dapat dievaluasi pada awal eksekusi, dan tabel sementara dapat diperiksa terhadap masing-masing nilai dalam pemilihan luar, daripada menjalankan kembali subselect setiap kali seperti yang diperlukan dengan NOT EXISTSpernyataan.

Jika subquery harus dikorelasikan dengan pemilihan luar, maka NOT EXISTSmungkin lebih disukai, karena pengoptimal dapat menemukan penyederhanaan yang mencegah pembuatan tabel sementara untuk melakukan fungsi yang sama.

Jeffrey L Whitledge
sumber
6

Saya menggunakan

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

dan menemukan bahwa itu memberikan hasil yang salah (Secara salah saya maksud tidak ada hasil). Karena ada NULL di TABLE2.Col1.

Saat mengubah kueri ke

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

memberi saya hasil yang benar.

Sejak itu saya mulai menggunakan TIDAK ADA di mana-mana.

ravish.hacker
sumber
5

Mereka sangat mirip tetapi tidak benar-benar sama.

Dalam hal efisiensi, saya menemukan bahwa join kiri adalah pernyataan nol yang lebih efisien (ketika banyak baris yang harus dipilih)

Onga Leo-Yoda Vellem
sumber
2

Jika pengoptimal mengatakan mereka sama maka pertimbangkan faktor manusia. Saya lebih suka melihat TIDAK ADA :)

suatu hari nanti
sumber
1

Ini adalah pertanyaan yang sangat bagus, jadi saya memutuskan untuk menulis artikel yang sangat rinci tentang topik ini di blog saya.

Model tabel basis data

Mari kita asumsikan kita memiliki dua tabel berikut dalam database kita, yang membentuk hubungan tabel satu-ke-banyak.

Tabel SQL EXISTS

The studenttabel orangtua, dan student_gradeadalah tabel anak karena memiliki kolom student_id Key Foreign referensi id Primary Key kolom dalam tabel mahasiswa.

Ini student tableberisi dua catatan berikut:

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

Dan, student_grademeja menyimpan nilai yang diterima siswa:

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

Katakanlah kita ingin mendapatkan semua siswa yang telah menerima nilai 10 di kelas Matematika.

Jika kami hanya tertarik pada pengidentifikasi siswa, maka kami dapat menjalankan kueri seperti ini:

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

Tapi, aplikasi ini tertarik untuk menampilkan nama lengkap a student, bukan hanya pengenal, jadi kita perlu info dari studenttabel juga.

Untuk memfilter studentcatatan yang memiliki nilai 10 dalam Matematika, kita dapat menggunakan operator SQL EXIS, seperti ini:

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

Saat menjalankan kueri di atas, kita dapat melihat bahwa hanya baris Alice yang dipilih:

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

Kueri luar memilih studentkolom baris yang ingin kami kembalikan ke klien. Namun, klausa WHERE menggunakan operator EXISTS dengan subquery dalam yang terkait.

Operator EXISTS mengembalikan true jika subquery mengembalikan setidaknya satu record dan false jika tidak ada baris yang dipilih. Mesin basis data tidak harus menjalankan subquery sepenuhnya. Jika satu catatan cocok, operator EXISTS mengembalikan true, dan baris kueri lainnya yang terkait dipilih.

Subquery dalam dikorelasikan karena kolom student_id dari student_gradetabel dicocokkan dengan kolom id dari tabel siswa luar.

SQL TIDAK ADA

Mari kita pertimbangkan bahwa kita ingin memilih semua siswa yang tidak memiliki nilai lebih rendah dari 9. Untuk ini, kita dapat menggunakan TIDAK ADA, yang meniadakan logika operator EXISTS.

Oleh karena itu, operator NOT EXISTS mengembalikan true jika subquery yang mendasarinya tidak mengembalikan catatan. Namun, jika catatan tunggal dicocokkan dengan subquery batin, operator BUKAN ada akan kembali palsu, dan eksekusi subquery dapat dihentikan.

Untuk mencocokkan semua catatan siswa yang tidak memiliki tingkat student_grade terkait dengan nilai lebih rendah dari 9, kita dapat menjalankan kueri SQL berikut:

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

Saat menjalankan kueri di atas, kita dapat melihat bahwa hanya catatan Alice yang cocok:

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

Jadi, keuntungan menggunakan operator SQL EXIS dan NOT EXISTS adalah bahwa eksekusi subquery bagian dalam dapat dihentikan selama ditemukan catatan yang cocok.

Vlad Mihalcea
sumber
-1

Tergantung..

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

tidak akan relatif lambat, tidak banyak untuk membatasi ukuran dari pemeriksaan kueri untuk melihat apakah mereka kunci. Ada akan lebih disukai dalam kasus ini.

Tetapi, tergantung pada pengoptimal DBMS, ini bisa tidak berbeda.

Sebagai contoh saat EXISTS lebih baik

SELECT x.col
FROM big_table x
WHERE EXISTS( SELECT key FROM really_big_table WHERE key = x.key);
  AND id = very_limiting_criteria
Greg Ogle
sumber
1
INdan EXISTS dapatkan paket yang sama di SQL Server . Pertanyaannya adalah tentang NOT INvs NOT EXISTSpula.
Martin Smith