SQL "pilih di mana tidak dalam subquery" tidak mengembalikan hasil

130

Penafian: Saya telah menemukan masalah (saya pikir), tetapi saya ingin menambahkan masalah ini ke Stack Overflow karena saya tidak dapat (dengan mudah) menemukannya di mana pun. Juga, seseorang mungkin memiliki jawaban yang lebih baik daripada saya.

Saya memiliki database di mana satu tabel "Biasa" dirujuk oleh beberapa tabel lainnya. Saya ingin melihat catatan apa di tabel Common yatim (yaitu, tidak memiliki referensi dari tabel lain).

Saya menjalankan kueri ini:

select *
from Common
where common_id not in (select common_id from Table1)
and common_id not in (select common_id from Table2)

Saya tahu bahwa ada catatan yatim, tetapi tidak ada catatan yang dikembalikan. Kenapa tidak?

(Ini SQL Server, jika itu penting.)

Jeremy Stein
sumber
Stackoverflow.com/a/129152/1667619 ini menjawab pertanyaan MENGAPA dengan cukup baik.
Ruchan

Jawaban:

234

Memperbarui:

Artikel-artikel ini di blog saya menjelaskan perbedaan antara metode-metode ini secara lebih rinci:


Ada tiga cara untuk melakukan permintaan seperti itu:

  • LEFT JOIN / IS NULL:

    SELECT  *
    FROM    common
    LEFT JOIN
            table1 t1
    ON      t1.common_id = common.common_id
    WHERE   t1.common_id IS NULL
  • NOT EXISTS:

    SELECT  *
    FROM    common
    WHERE   NOT EXISTS
            (
            SELECT  NULL
            FROM    table1 t1
            WHERE   t1.common_id = common.common_id
            )
  • NOT IN:

    SELECT  *
    FROM    common
    WHERE   common_id NOT IN
            (
            SELECT  common_id
            FROM    table1 t1
            )

Ketika table1.common_idtidak dapat dibatalkan, semua kueri ini secara semantik sama.

Ketika nullable, NOT INberbeda, karena IN(dan, karenanya, NOT IN) kembali NULLketika nilai tidak cocok dengan apa pun dalam daftar yang berisi a NULL.

Ini mungkin membingungkan tetapi mungkin menjadi lebih jelas jika kita mengingat sintaks alternatif untuk ini:

common_id = ANY
(
SELECT  common_id
FROM    table1 t1
)

Hasil dari kondisi ini adalah produk boolean dari semua perbandingan dalam daftar. Tentu saja, NULLnilai tunggal menghasilkanNULL hasil yang menjadikan keseluruhan hasil NULLjuga.

Kita tidak pernah bisa mengatakan hal itu dengan pasti common_id itu tidak sama dengan apa pun dari daftar ini, karena setidaknya salah satu nilainya NULL.

Misalkan kita memiliki data ini:

common

--
1
3

table1

--
NULL
1
2

LEFT JOIN / IS NULLdan NOT EXISTSakan kembali 3, tidak NOT INakan mengembalikan apa pun (karena akan selalu dievaluasi jugaFALSE atau NULL).

Dalam MySQL, dalam kasus pada kolom non-nullable, LEFT JOIN / IS NULLdan NOT INsedikit (beberapa persen) lebih efisien daripadaNOT EXISTS . Jika kolom dapat dibatalkan, NOT EXISTSapakah yang paling efisien (sekali lagi, tidak banyak).

Dalam Oracle, ketiga pertanyaan menghasilkan rencana yang sama (aANTI JOIN ).

Di SQL Server, NOT IN/ NOT EXISTSlebih efisien, karena LEFT JOIN / IS NULLtidak dapat dioptimalkan keANTI JOIN oleh pengoptimalnya.

Di PostgreSQL, LEFT JOIN / IS NULLdan NOT EXISTSlebih efisien daripada NOT IN, mereka dioptimalkan untuk Anti Join, sementara NOT INmenggunakan hashed subplan(atau bahkan dataran subplanjika subquery terlalu besar untuk hash)

Quassnoi
sumber
8
Jawaban bagus! Terima kasih!
StevenMcD
ini luar biasa dan sangat membantu
kavun
1
+1 karena, empat setengah tahun kemudian, jawaban ini membantu saya keluar dengan masalah yang membuat saya bingung!
Carson63000
@ Carson63000 Snap! Saya pikir saya sudah gila sebelum melihat jawaban ini
Bobby
1
@IstiaqueAhmed: NOT EXISTSmengevaluasi ke TRUE jika kueri di dalamnya mengembalikan setiap baris. SELECT NULLbisa jadi SELECT *atau SELECT 1atau apa pun, NOT EXISTSpredikat tidak melihat nilai-nilai baris, hanya menghitungnya.
Quassnoi
36

Jika Anda ingin dunia menjadi tempat boolean dua nilai, Anda harus mencegah sendiri kasus nol (nilai ketiga).

Jangan menulis klausa IN yang memungkinkan null di sisi daftar. Saring mereka!

common_id not in
(
  select common_id from Table1
  where common_id is not null
)
Amy B
sumber
6
nulls di dalam-klausa-daftar adalah alasan umum untuk hasil pencarian yang hilang.
Amy B
'Ketika membandingkan dengan nol, jawabannya tidak diketahui' - dari jawaban oleh @Jeremy Stein. Dari common_id not in, kita masih dapat memiliki common_idnilai itu NULL. Jadi bukankah masalah tidak mendapatkan hasil masih bertahan?
Istiaque Ahmed
5

Table1 atau Table2 memiliki beberapa nilai null untuk common_id. Gunakan kueri ini sebagai gantinya:

select *
from Common
where common_id not in (select common_id from Table1 where common_id is not null)
and common_id not in (select common_id from Table2 where common_id is not null)
Jeremy Stein
sumber
1
Bagaimana jika ada data dalam satu tabel tetapi tidak yang lain? Apakah Anda ingin "dan" atau "atau" di sana?
Philip Kelley
1
Saya mencari catatan yang tidak dirujuk dalam tabel apa pun, jadi saya ingin DAN. Saya akan mengklarifikasi pertanyaannya.
Jeremy Stein
4
select *
from Common c
where not exists (select t1.commonid from table1 t1 where t1.commonid = c.commonid)
and not exists (select t2.commonid from table2 t2 where t2.commonid = c.commonid)
patmortech
sumber
4

Tak jauh dari kepala saya ...

select c.commonID, t1.commonID, t2.commonID
from Common c
     left outer join Table1 t1 on t1.commonID = c.commonID
     left outer join Table2 t2 on t2.commonID = c.commonID
where t1.commonID is null 
     and t2.commonID is null

Saya menjalankan beberapa tes dan inilah hasil jawaban wrt @ patmortech dan komentar @ rexem.

Jika Table1 atau Table2 tidak diindeks pada commonID, Anda mendapatkan pemindaian tabel tetapi permintaan @ patmortech masih dua kali lebih cepat (untuk tabel master baris 100 ribu).

Jika tidak ada yang diindeks pada commonID, Anda mendapatkan dua pemindaian tabel dan perbedaannya dapat diabaikan.

Jika keduanya diindeks pada commonID, kueri "tidak ada" berjalan pada 1/3 waktu.

Austin Salonen
sumber
1
Itu harus menjadi DAN di mana klausa. Kalau tidak, itu berhasil.
Jeremy Stein
1
berubah per komentar Anda. "Atau" memilih anak yatim di kedua meja.
Austin Salonen
1
Itu lebih baik. Ngomong-ngomong, apakah ada alasan mengapa saya harus menggunakan gabungan luar daripada subquery?
Jeremy Stein
3
Keterbacaan adalah yang utama. Saya menduga rencana eksekusi yang lebih baik akan dihasilkan tetapi tanpa rencana permintaan, saya tidak dapat mengonfirmasi.
Austin Salonen
2
Pendekatan ini lebih buruk daripada menggunakan TIDAK ADA - hasil gabungan dalam mengambil lebih banyak baris daripada yang dibutuhkan, maka hasil dibandingkan untuk kolom menjadi nol. Dan BUKAN ADA lebih mudah dibaca untuk boot.
OMG Ponies
3
SELECT T.common_id
  FROM Common T
       LEFT JOIN Table1 T1 ON T.common_id = T1.common_id
       LEFT JOIN Table2 T2 ON T.common_id = T2.common_id
 WHERE T1.common_id IS NULL
   AND T2.common_id IS NULL
manji
sumber
1
Pendekatan ini lebih buruk daripada menggunakan TIDAK ADA - hasil gabungan dalam mengambil lebih banyak baris daripada yang dibutuhkan, maka hasil dibandingkan untuk kolom menjadi nol. Ini bekerja, tetapi kinerjanya tidak akan sebaik - mungkin lebih buruk daripada menggunakan IN dengan subqueries yang berhubungan.
OMG Ponies
3

Anggaplah nilai-nilai ini untuk common_id:

Common - 1
Table1 - 2
Table2 - 3, null

Kami ingin baris dalam Common untuk kembali, karena tidak ada di salah satu tabel lainnya. Namun, null melempar kunci inggris.

Dengan nilai-nilai itu, kueri setara dengan:

select *
from Common
where 1 not in (2)
and 1 not in (3, null)

Itu setara dengan:

select *
from Common
where not (1=2)
and not (1=3 or 1=null)

Di sinilah masalahnya dimulai. Saat membandingkan dengan nol, jawabannya tidak diketahui . Jadi kueri berkurang menjadi

select *
from Common
where not (false)
and not (false or unkown)

salah atau tidak dikenal tidak diketahui:

select *
from Common
where true
and not (unknown)

true dan not unkown juga tidak dikenal:

select *
from Common
where unknown

Kondisi di mana tidak mengembalikan catatan di mana hasilnya tidak diketahui, jadi kami tidak mendapatkan catatan kembali.

Salah satu cara untuk mengatasi ini adalah dengan menggunakan operator yang ada daripada masuk. Ada yang tidak pernah mengembalikan unkown karena beroperasi pada baris daripada kolom. (Baris ada atau tidak; tidak ada satupun ambiguitas nol ini di level baris!)

select *
from Common
where not exists (select common_id from Table1 where common_id = Common.common_id)
and not exists (select common_id from Table2 where common_id = Common.common_id)
Jeremy Stein
sumber
2

ini bekerja untuk saya :)

pilih * dari Umum

dimana

common_id tidak dalam (pilih ISNULL (common_id, 'dummy-data') dari Table1)

dan common_id tidak dalam (pilih ISNULL (common_id, 'dummy-data') dari Table2)

melengkung
sumber
@marlar, sub-kueri selalu mengembalikan 1 atau 0, bukan daftar nilai. Jadi bagaimana kinerja di NOT INsana?
Istiaque Ahmed
0
select *,
(select COUNT(ID)  from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun 
from CategoryMaster
Donga jayesh
sumber
0

Saya punya contoh di mana saya melihat ke atas dan karena satu meja memegang nilai sebagai ganda, yang lain sebagai string, mereka tidak akan cocok (atau tidak cocok tanpa pemain). Tapi hanya TIDAK DI . Sebagai SELECT ... DI ... bekerja. Aneh, tapi kupikir aku akan berbagi kalau-kalau ada orang lain yang menemukan perbaikan sederhana ini.

tebusan
sumber
0

Silakan ikuti contoh di bawah ini untuk memahami topik di atas:

Anda juga dapat mengunjungi tautan berikut untuk mengetahui Anti gabung

select department_name,department_id from hr.departments dep
where not exists 
    (select 1 from hr.employees emp
    where emp.department_id=dep.department_id
    )
order by dep.department_name;
DEPARTMENT_NAME DEPARTMENT_ID
Benefits    160
Construction    180
Contracting 190
.......

Tetapi jika kita menggunakan NOT INdalam kasus itu kita tidak mendapatkan data apa pun.

select Department_name,department_id from hr.departments dep 
where department_id not in (select department_id from hr.employees );

tidak ada data ditemukan

Ini terjadi karena ( select department_id from hr.employees) mengembalikan nilai nol dan seluruh kueri dievaluasi sebagai salah. Kita dapat melihatnya jika kita mengubah SQL sedikit seperti di bawah ini dan menangani nilai nol dengan fungsi NVL.

select Department_name,department_id from hr.departments dep 
where department_id not in (select NVL(department_id,0) from hr.employees )

Sekarang kami mendapatkan data:

DEPARTMENT_NAME DEPARTMENT_ID
Treasury    120
Corporate Tax   130
Control And Credit  140
Shareholder Services    150
Benefits    160
....

Sekali lagi kami mendapatkan data karena kami telah menangani nilai nol dengan fungsi NVL.

Rajesh Sarkar
sumber
Hasil SQl tidak muncul dalam bentuk tabel, mohon jelaskan kepada saya.
Rajesh Sarkar