Nilai NULL di dalam NOT IN clause

244

Masalah ini muncul ketika saya mendapat catatan yang berbeda menghitung untuk apa yang saya pikir adalah pertanyaan yang identik satu menggunakan not in wherekendala dan yang lainnya a left join. Tabel dalam not inbatasan memiliki satu nilai nol (data buruk) yang menyebabkan kueri untuk mengembalikan hitungan 0 catatan. Saya agak mengerti mengapa tetapi saya bisa menggunakan bantuan untuk sepenuhnya memahami konsep ini.

Untuk menyatakannya secara sederhana, mengapa kueri A mengembalikan hasil tetapi B tidak?

A: select 'true' where 3 in (1, 2, 3, null)
B: select 'true' where 3 not in (1, 2, null)

Ini di SQL Server 2005. Saya juga menemukan bahwa panggilan set ansi_nulls offmenyebabkan B mengembalikan hasil.

Ide Jamie
sumber

Jawaban:

283

Kueri A sama dengan:

select 'true' where 3 = 1 or 3 = 2 or 3 = 3 or 3 = null

Karena 3 = 3itu benar, Anda mendapatkan hasil.

Kueri B sama dengan:

select 'true' where 3 <> 1 and 3 <> 2 and 3 <> null

Ketika ansi_nullsaktif, 3 <> nulladalah UNKNOWN, sehingga mengevaluasi predikat untuk UNKNOWN, dan Anda tidak mendapatkan setiap baris.

Ketika ansi_nullstidak aktif, 3 <> nullitu benar, sehingga predikat mengevaluasi ke true, dan Anda mendapatkan baris.

Brannon
sumber
11
Adakah yang pernah menunjukkan bahwa mengubah NOT INserangkaian <> andperubahan perilaku semantik tidak pada set ini menjadi sesuatu yang lain?
Ian Boyd
8
@Ian - Sepertinya "A NOT IN ('X', 'Y')" sebenarnya adalah alias untuk A <> 'X' AND A <> 'Y' dalam SQL. (Saya melihat bahwa Anda menemukan ini sendiri di stackoverflow.com/questions/3924694/... , tetapi ingin memastikan keberatan Anda diatasi dalam pertanyaan ini.)
Ryan Olson
Saya kira ini menjelaskan mengapa SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);menghasilkan baris alih-alih hasil kosong yang saya harapkan.
binki
2
Ini adalah perilaku SQL server yang sangat buruk, karena jika mengharapkan perbandingan NULL menggunakan "IS NULL", maka itu harus memperluas klausa IN ke perilaku yang sama, dan tidak bodoh menerapkan semantik yang salah untuk dirinya sendiri.
OzrenTkalcecKrznaric
@binki, Permintaan Anda dijalankan jika dijalankan di sini rextester.com/l/sql_server_online_compiler tetapi tidak berfungsi jika dijalankan di sini sqlcourse.com/cgi-bin/interpreter.cgi .
Istiaque Ahmed
52

Setiap kali Anda menggunakan NULL Anda benar-benar berurusan dengan logika Tiga Nilai.

Kueri pertama Anda mengembalikan hasil ketika klausa WHERE mengevaluasi ke:

    3 = 1 or 3 = 2 or 3 = 3 or 3 = null
which is:
    FALSE or FALSE or TRUE or UNKNOWN
which evaluates to 
    TRUE

Yang kedua:

    3 <> 1 and 3 <> 2 and 3 <> null
which evaluates to:
    TRUE and TRUE and UNKNOWN
which evaluates to:
    UNKNOWN

UNKNOWN tidak sama dengan FALSE, Anda dapat dengan mudah mengujinya dengan menelepon:

select 'true' where 3 <> null
select 'true' where not (3 <> null)

Kedua pertanyaan tidak akan memberi Anda hasil

Jika UNKNOWN sama dengan FALSE maka dengan asumsi bahwa kueri pertama akan memberi Anda SALAH, yang kedua harus mengevaluasi ke TRUE karena akan sama dengan TIDAK (FALSE).
Bukan itu masalahnya.

Ada artikel yang sangat bagus tentang hal ini di SqlServerCentral .

Seluruh masalah NULLs dan Three-Valued Logic dapat sedikit membingungkan pada awalnya tetapi penting untuk dipahami untuk menulis kueri yang benar di TSQL

Artikel lain yang saya rekomendasikan adalah Fungsi SQL Agregat dan NULL .

kristof
sumber
33

NOT IN mengembalikan 0 catatan bila dibandingkan dengan nilai yang tidak diketahui

Karena NULLtidak diketahui, NOT INkueri yang berisi a NULLatau NULLs dalam daftar nilai yang mungkin akan selalu mengembalikan 0catatan karena tidak ada cara untuk memastikan bahwa NULLnilainya bukan nilai yang sedang diuji.

YonahW
sumber
3
Ini jawaban singkatnya. Saya menemukan ini lebih mudah dipahami bahkan tanpa contoh.
Govind Rai
18

Bandingkan dengan nol tidak ditentukan, kecuali jika Anda menggunakan IS NULL.

Jadi, ketika membandingkan 3 dengan NULL (permintaan A), ia mengembalikan tidak terdefinisi.

Yaitu SELECT 'true' di mana 3 in (1,2, null) dan SELECT 'true' di mana 3 in (1,2, null)

akan menghasilkan hasil yang sama, karena TIDAK (TIDAK DIKENAL) masih tidak didefinisikan, tetapi tidak BENAR

Sunny Milenov
sumber
Poin yang bagus. pilih 1 di mana null in (null) tidak mengembalikan baris (ansi).
crokusek
9

Judul pertanyaan ini pada saat penulisan adalah

Nilai TIDAK di kendala dan NULL SQL

Dari teks pertanyaan tampak bahwa masalah itu terjadi dalam SELECTpermintaan SQL DML , bukan SQL DDL CONSTRAINT.

Namun, terutama mengingat kata-kata dari judulnya, saya ingin menunjukkan bahwa beberapa pernyataan yang dibuat di sini berpotensi pernyataan yang menyesatkan, yaitu yang sejalan dengan (parafrase)

Ketika predikat mengevaluasi ke UNKNOWN Anda tidak mendapatkan baris.

Meskipun ini adalah kasus untuk DML SQL, ketika mempertimbangkan kendala efeknya berbeda.

Pertimbangkan tabel yang sangat sederhana ini dengan dua kendala yang diambil langsung dari predikat dalam pertanyaan (dan ditangani dalam jawaban yang sangat baik oleh @Brannon):

DECLARE @T TABLE 
(
 true CHAR(4) DEFAULT 'true' NOT NULL, 
 CHECK ( 3 IN (1, 2, 3, NULL )), 
 CHECK ( 3 NOT IN (1, 2, NULL ))
);

INSERT INTO @T VALUES ('true');

SELECT COUNT(*) AS tally FROM @T;

Sesuai jawaban @ Brannon, kendala pertama (menggunakan IN) mengevaluasi ke TRUE dan kendala kedua (menggunakan NOT IN) mengevaluasi ke UNKNOWN. Namun , sisipan berhasil! Oleh karena itu, dalam hal ini tidak sepenuhnya benar untuk mengatakan, "Anda tidak mendapatkan baris" karena kami memang mendapat baris yang dimasukkan sebagai hasilnya.

Efek di atas memang yang benar sehubungan dengan SQL-92 Standard. Bandingkan dan bandingkan bagian berikut dari SQL-92 spec

7.6 di mana klausa

Hasil dari adalah tabel dari baris-baris T yang hasilnya benar.

4.10 Batasan integritas

Kendala pemeriksaan tabel terpenuhi jika dan hanya jika kondisi pencarian yang ditentukan tidak salah untuk setiap baris tabel.

Dengan kata lain:

Dalam SQL DML, baris dihapus dari hasil ketika WHEREmengevaluasi ke UNKNOWN karena tidak memenuhi kondisi "benar".

Dalam SQL DDL (yaitu kendala), baris tidak dihapus dari hasil ketika mereka mengevaluasi untuk UNKNOWN karena tidak memenuhi kondisi "tidak salah".

Meskipun efek dalam SQL DML dan SQL DDL masing-masing mungkin tampak kontradiktif, ada alasan praktis untuk memberikan hasil UNKNOWN 'manfaat keraguan' dengan memungkinkan mereka untuk memenuhi kendala (lebih tepat, memungkinkan mereka untuk tidak gagal memenuhi kendala) : tanpa perilaku ini, setiap kendala harus secara eksplisit menangani nol dan itu akan sangat tidak memuaskan dari perspektif desain bahasa (belum lagi, rasa sakit yang tepat untuk coders!)

ps jika Anda menemukan itu sebagai tantangan untuk mengikuti logika seperti "tidak diketahui tidak gagal untuk memenuhi kendala" karena saya menulisnya, maka pertimbangkan Anda dapat membuang semua ini hanya dengan menghindari kolom nullable dalam SQL DDL dan apa pun dalam SQL DML yang menghasilkan nulls (misalnya gabungan luar)!

suatu hari nanti
sumber
Jujur saya tidak berpikir ada yang tersisa untuk dikatakan tentang hal ini. Menarik.
Jamie Ide
2
@Jamie Ide: Sebenarnya, saya punya jawaban lain tentang masalah ini: karena NOT IN (subquery)melibatkan nol dapat memberikan hasil yang tidak terduga, tergoda untuk menghindari IN (subquery)sepenuhnya dan selalu menggunakan NOT EXISTS (subquery)(seperti yang pernah saya lakukan!) Karena tampaknya selalu menangani nol dengan benar. Namun, ada beberapa kasus di mana NOT IN (subquery)memberikan hasil yang diharapkan sedangkan NOT EXISTS (subquery)memberikan hasil yang tidak terduga! Saya mungkin belum menulis ini jika saya dapat menemukan catatan saya pada subjek (perlu catatan karena tidak intuitif!) Kesimpulannya sama, namun: hindari nol!
onedaywhen
@oneday ketika saya bingung dengan pernyataan Anda bahwa NULL perlu dibuat khusus untuk memiliki perilaku yang konsisten (konsisten secara internal, tidak konsisten dengan spesifikasi). Apakah tidak cukup untuk mengubah 4.10 menjadi "Batasan pemeriksaan tabel puas jika dan hanya jika kondisi pencarian yang ditentukan benar"?
DylanYoung
@DylanYoung: Tidak, spek diucapkan seperti itu untuk alasan penting: SQL menderita dari tiga logika nilai, di mana nilai tersebut berada TRUE, FALSEdan UNKNOWN. Saya kira 4.10 dapat membaca, "Kendala pemeriksaan tabel terpenuhi jika dan hanya jika kondisi pencarian yang ditentukan adalah BENAR atau TIDAK DIKETAHUI untuk setiap baris tabel" - perhatikan perubahan pada akhir kalimat - yang Anda hapus - - dari "untuk apa pun" ke "untuk semua". Saya merasa perlu untuk memanfaatkan nilai-nilai logis karena makna 'benar' dan 'salah' dalam bahasa alami pastilah merujuk pada logika dua nilai klasik.
onedaywhen
1
Pertimbangkan: CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );- maksudnya di sini adalah bharus sama aatau nol. Jika suatu kendala harus menghasilkan TRUE untuk dipenuhi maka kita perlu mengubah kendala untuk secara eksplisit menangani nulls misalnya CHECK( a = b OR b IS NULL ). Dengan demikian, setiap kendala harus memiliki ...OR IS NULLlogika yang ditambahkan oleh pengguna untuk setiap kolom nullable yang terlibat: lebih rumit, lebih banyak bug ketika mereka lupa melakukannya, dll. Jadi saya pikir komite standar SQL hanya mencoba untuk menjadi pragmatis.
onedaywhen
7

Dalam A, 3 diuji untuk kesetaraan terhadap setiap anggota set, menghasilkan (FALSE, FALSE, TRUE, UNKNOWN). Karena salah satu elemennya BENAR, maka kondisinya BENAR. (Mungkin juga terjadi hubungan arus pendek di sini, jadi itu benar-benar berhenti segera setelah menyentuh TRUE pertama dan tidak pernah mengevaluasi 3 = NULL.)

Dalam B, saya pikir itu mengevaluasi kondisi sebagai TIDAK (3 in (1,2, null)). Menguji 3 untuk kesetaraan terhadap hasil yang ditetapkan (FALSE, FALSE, UNKNOWN), yang dikumpulkan ke UNKNOWN. TIDAK (TIDAK DIKENALKAN) menghasilkan TIDAK DIKETAHUI. Jadi secara keseluruhan kebenaran kondisi tersebut tidak diketahui, yang pada dasarnya diperlakukan sebagai SALAH.

Dave Costa
sumber
7

Dapat disimpulkan dari jawaban di sini yang NOT IN (subquery)tidak menangani nulls dengan benar dan harus dihindari NOT EXISTS. Namun, kesimpulan seperti itu mungkin prematur. Dalam skenario berikut, dikreditkan ke Chris Date (Pemrograman dan Desain Basis Data, Vol 2 No 9, September 1989), itu NOT INyang menangani nulls dengan benar dan mengembalikan hasil yang benar, daripada NOT EXISTS.

Pertimbangkan tabel spuntuk mewakili pemasok ( sno) yang dikenal memasok bagian ( pno) dalam jumlah ( qty). Tabel saat ini memuat nilai-nilai berikut:

      VALUES ('S1', 'P1', NULL), 
             ('S2', 'P1', 200),
             ('S3', 'P1', 1000)

Perhatikan bahwa kuantitas dapat dibatalkan yaitu untuk dapat mencatat fakta bahwa pemasok diketahui memasok suku cadang meskipun tidak diketahui dalam jumlah berapa.

Tugasnya adalah menemukan pemasok yang dikenal sebagai nomor bagian persediaan 'P1' tetapi tidak dalam jumlah 1000.

Penggunaan berikut NOT INuntuk mengidentifikasi dengan benar pemasok 'S2' saja:

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1', NULL ), 
                       ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT DISTINCT spx.sno
  FROM sp spx
 WHERE spx.pno = 'P1'
       AND 1000 NOT IN (
                        SELECT spy.qty
                          FROM sp spy
                         WHERE spy.sno = spx.sno
                               AND spy.pno = 'P1'
                       );

Namun, kueri di bawah ini menggunakan struktur umum yang sama tetapi dengan NOT EXISTStetapi salah memasukkan pemasok 'S1' dalam hasilnya (yaitu yang kuantitasnya nol):

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1', NULL ), 
                       ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT DISTINCT spx.sno
  FROM sp spx
 WHERE spx.pno = 'P1'
       AND NOT EXISTS (
                       SELECT *
                         FROM sp spy
                        WHERE spy.sno = spx.sno
                              AND spy.pno = 'P1'
                              AND spy.qty = 1000
                      );

Jadi NOT EXISTSbukan peluru perak yang mungkin muncul!

Tentu saja, sumber masalahnya adalah keberadaan nulls, oleh karena itu solusi 'nyata' adalah untuk menghilangkan nulls tersebut.

Ini dapat dicapai (di antara kemungkinan desain lainnya) menggunakan dua tabel:

  • sp pemasok yang diketahui memasok suku cadang
  • spq pemasok diketahui memasok suku cadang dalam jumlah yang dikenal

mencatat mungkin harus ada batasan kunci asing di mana spqreferensi sp.

Hasilnya kemudian dapat diperoleh dengan menggunakan operator relasional 'minus' (menjadi EXCEPTkata kunci dalam SQL Standar) misalnya

WITH sp AS 
     ( SELECT * 
         FROM ( VALUES ( 'S1', 'P1' ), 
                       ( 'S2', 'P1' ),
                       ( 'S3', 'P1' ) )
              AS T ( sno, pno )
     ),
     spq AS 
     ( SELECT * 
         FROM ( VALUES ( 'S2', 'P1', 200 ),
                       ( 'S3', 'P1', 1000 ) )
              AS T ( sno, pno, qty )
     )
SELECT sno
  FROM spq
 WHERE pno = 'P1'
EXCEPT 
SELECT sno
  FROM spq
 WHERE pno = 'P1'
       AND qty = 1000;
suatu hari nanti
sumber
1
Oh Tuhan. Terima kasih untuk benar-benar menulis ini .... ini membuatku gila ..
Govind Rai
6

Null menandakan dan tidak adanya data, yaitu tidak diketahui, bukan nilai data apa pun. Sangat mudah bagi orang-orang dari latar belakang pemrograman untuk membingungkan ini karena dalam bahasa tipe C saat menggunakan pointer nol memang bukan apa-apa.

Oleh karena itu dalam kasus pertama 3 memang di set (1,2,3, null) jadi benar dikembalikan

Namun dalam kedua Anda bisa menguranginya menjadi

pilih 'true' di mana 3 tidak dalam (null)

Jadi tidak ada yang dikembalikan karena parser tidak tahu tentang set yang Anda bandingkan - itu bukan set kosong tapi set yang tidak diketahui. Menggunakan (1, 2, null) tidak membantu karena set (1,2) jelas salah, tapi kemudian Anda dan itu menentang yang tidak diketahui, yang tidak diketahui.

Cruachan
sumber
6

JIKA Anda ingin memfilter dengan NOT IN untuk subquery containg NULLs justcheck for not null

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )
Mihai
sumber
Saya punya masalah dengan permintaan gabung luar yang tidak mengembalikan catatan dalam situasi khusus, Jadi periksa solusi ini untuk skenario Null dan ada catatan dan itu berhasil untuk saya, Jika masalah lain terjadi saya akan disebutkan di sini, Terima kasih banyak.
QMaster
1

ini untuk Boy:

select party_code 
from abc as a
where party_code not in (select party_code 
                         from xyz 
                         where party_code = a.party_code);

ini berfungsi terlepas dari pengaturan ansi

CB
sumber
untuk pertanyaan awal: B: pilih 'true' di mana 3 tidak dalam (1, 2, null) cara untuk menghapus null harus dilakukan, misalnya pilih 'true' di mana 3 tidak dalam (1, 2, isnull (null, 0) ) logika keseluruhannya adalah, jika NULL adalah penyebabnya, kemudian temukan cara untuk menghapus nilai NULL pada beberapa langkah dalam kueri.
pilih party_code dari abc sebagai tempat di mana party_code tidak (pilih party_code dari xyz di mana party_code tidak nol) tetapi semoga berhasil jika Anda lupa bidang memungkinkan nulls, yang sering terjadi
1

SQL menggunakan logika bernilai tiga untuk nilai kebenaran. The INpermintaan menghasilkan hasil yang diharapkan:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE col IN (NULL, 1)
-- returns first row

Tetapi menambahkan NOTtidak membalikkan hasilnya:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT col IN (NULL, 1)
-- returns zero rows

Ini karena kueri di atas setara dengan yang berikut:

SELECT * FROM (VALUES (1), (2)) AS tbl(col) WHERE NOT (col = NULL OR col = 1)

Berikut adalah bagaimana klausa dievaluasi:

| col | col = NULL (1) | col = 1 | col = NULL OR col = 1 | NOT (col = NULL OR col = 1) |
|-----|----------------|---------|-----------------------|-----------------------------|
| 1   | UNKNOWN        | TRUE    | TRUE                  | FALSE                       |
| 2   | UNKNOWN        | FALSE   | UNKNOWN (2)           | UNKNOWN (3)                 |

Perhatikan itu:

  1. Perbandingan melibatkan NULLhasilUNKNOWN
  2. The ORekspresi di mana tak satu pun dari operan TRUEdan setidaknya satu operan adalah UNKNOWNhasil UNKNOWN( ref )
  3. The NOTdari UNKNOWNhasil UNKNOWN( ref )

Anda dapat memperluas contoh di atas hingga lebih dari dua nilai (mis. NULL, 1 dan 2) tetapi hasilnya akan sama: jika salah satu dari nilai tersebut NULLmaka tidak ada baris yang cocok.

Salman A
sumber