NOT IN yan tümcesinde NULL değerler


245

Bu sorun, biri not in wherekısıtlama ve diğeri a kullanarak özdeş sorgular olduğunu düşündüğüm için farklı kayıt sayıları aldığımda ortaya çıktı left join. not inKısıtlamadaki tabloda, bir boş değer (hatalı veriler) vardı ve bu da sorgunun 0 kayıt sayısını döndürmesine neden oldu. Nedenini biraz anlıyorum ama konsepti tam olarak kavramak için biraz yardım alabilirim.

Basitçe ifade etmek gerekirse, A sorgusu neden bir sonuç döndürüyor ancak B vermiyor?

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

Bu SQL Server 2005 üzerinde oldu. Ayrıca arama set ansi_nulls offB sonucu döndürmek neden bulundu .

Yanıtlar:


283

A sorgusu aşağıdakilerle aynıdır:

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

Yana 3 = 3doğru, bir sonuç almak.

B sorgusu aşağıdakilerle aynıdır:

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

Açık olduğunda ansi_nulls, 3 <> nullBİLİNMİYOR, dolayısıyla yüklem BİLİNMEYEN olarak değerlendirilir ve herhangi bir satır almazsınız.

Ne zaman ansi_nullskapalı, 3 <> nullyüklem değerlendirir böylece true, doğrudur ve bir satır olsun.


11
Hiç kimse NOT INbir dizi <> anddeğişikliğe dönüştürmenin bu sette değil anlamsal davranışını başka bir şeye dönüştürdüğüne dikkat çekti mi?
Ian Boyd

8
@Ian - "A NOT IN ('X', 'Y')" aslında SQL'de A <> 'X' VE A <> 'Y' için bir takma ad gibi görünüyor. (Bunu kendiniz stackoverflow.com/questions/3924694/… 'da keşfettiğinizi , ancak itirazınızın bu soruda ele alındığından emin olmak istediğinizi görüyorum .)
Ryan Olson

Sanırım bu SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);beklediğim boş sonuç kümesi yerine neden satır verdiğini açıklıyor .
binki

2
Bu SQL sunucusunun çok kötü bir davranışıdır, çünkü eğer "IS NULL" kullanarak NULL karşılaştırması beklerse, IN yantümcesini aynı davranışa genişletmeli ve aptalca yanlış semantiği kendine uygulamamalıdır.
OzrenTkalcecKrznaric

@binki, Burada rextester.com/l/sql_server_online_compiler çalıştırırsanız sorguyu çalıştırırsınız, ancak burada çalıştırılırsa sqlcourse.com/cgi-bin/interpreter.cgi çalıştırılmaz .
Istiaque Ahmed

53

NULL kullandığınızda gerçekten Üç Değerli bir mantıkla uğraşıyorsunuz demektir.

WHERE yan tümcesi şu şekilde değerlendirilirken, ilk sorgunuz sonuçları döndürür:

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

İkinci olan:

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

BİLİNMEYEN YANLIŞ ile aynı değil, arayarak kolayca test edebilirsiniz:

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

Her iki sorgu da size sonuç vermeyecektir

BİLİNMEYEN YANLIŞ ile aynıysa, ilk sorgunun size YANLIŞ vereceğini varsayarsak, ikincinin DOĞRU olarak DEĞER (YANLIŞ) ile aynı olması gerekir.
Durum böyle değil.

Bu konuda SqlServerCentral üzerinde çok iyi bir makale var .

NULL'lar ve Üç Değerli Mantık'ın tüm sayısı ilk başta biraz kafa karıştırıcı olabilir, ancak TSQL'de doğru sorguları yazmak için anlamak önemlidir

Ben tavsiye ederim başka bir makale SQL Toplama İşlevleri ve NULL olduğunu .


33

NOT IN bilinmeyen bir değerle karşılaştırıldığında 0 kayıt döndürür

Yana NULLbir bilinmeyen bir NOT INbir içeren sorgu NULLveya NULLolası değerler listesindeki s her zaman dönecektir 0emin olmak için bir yolu yoktur çünkü kayıtları NULLdeğer test edilen değeri değil.


3
Kısaca cevabı bu. Bunu örnek olmadan bile anlaması daha kolay buldum.
Govind Rai

18

IS NULL kullanmıyorsanız null ile karşılaştır, tanımsız.

Bu nedenle, 3 ile NULL (sorgu A) karşılaştırıldığında, tanımsız döndürür.

Ie 'true' (3) (1,2, null) ve SELECT 'true' (3, null)

NOT (TANIMLANMADI) hala tanımsız olduğu için DOĞRU değil aynı sonucu üretecek


Harika bir nokta. null içindeki (null) satırları (ansi) döndürmeyen 1'i seçin.
crokusek

9

Bu sorunun yazım sırasındaki başlığı

SQL NOT IN kısıtlaması ve NULL değerleri

Sorunun metninden, sorunun SELECTbir SQL DDL yerine bir SQL DML sorgusunda oluştuğu anlaşılıyor CONSTRAINT.

Bununla birlikte, özellikle başlığın ifadesi göz önüne alındığında, burada yapılan bazı ifadelerin potansiyel olarak yanıltıcı ifadeler olduğunu,

Yüklem BİLİNMEYEN olarak değerlendirildiğinde hiç satır almazsınız.

SQL DML için durum böyle olsa da, kısıtlamalar göz önüne alındığında etki farklıdır.

Doğrudan sorudaki tahminlerden alınan (ve @Brannon tarafından mükemmel bir cevapta ele alınan) iki kısıtlama içeren bu çok basit tabloyu düşünün:

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;

@ Brannon'un cevabına göre, ilk kısıtlama (kullanarak IN) DOĞRU olarak, ikinci kısıtlama (kullanarak NOT IN) BİLİNMEYEN olarak değerlendirilir. Ancak , ekleme başarılı! Bu nedenle, bu durumda, "satır almıyorsunuz" demek kesinlikle doğru değildir, çünkü sonuç olarak gerçekten bir satır ekledik.

Yukarıdaki etki gerçekten de SQL-92 Standardı için doğru etkidir. SQL-92 spesifikasyonunun aşağıdaki bölümünü karşılaştırın ve karşılaştırın

7.6 nerede fıkra

Bunun sonucu, arama koşulunun sonucunun doğru olduğu T satırlarının bir tablosudur.

4.10 Bütünlük kısıtlamaları

Bir tablo denetimi kısıtlaması, yalnızca belirtilen arama koşulu bir tablonun herhangi bir satırı için yanlış değilse karşılanır.

Diğer bir deyişle:

SQL DML satırlar, sonuç çıkarılır WHEREBİLİNMEYEN için değerlendirir çünkü vermez koşulunu karşılamak "doğrudur".

Çünkü onlar BİLİNMEYEN için değerlendirmek SQL DDL (yani kısıtlamalar), sıralar sonucu kaldırılmaz gelmez koşulunu karşılamak "yanlış değil".

Sırasıyla SQL DML ve SQL DDL'deki etkiler çelişkili görünse de, UNKNOWN sonuçlarına bir kısıtlamayı karşılamalarına izin vererek 'şüphenin yararı' vermenin pratik bir nedeni vardır (daha doğru bir şekilde, bir kısıtlamayı karşılayamamalarına izin vermek) : bu davranış olmadan, her kısıtlamanın null'ları açıkça ele alması gerekir ve bu dil tasarım perspektifinden çok tatmin edici olmaz (bahsetmiyorum bile, kodlayıcılar için doğru bir acı!)

ps Ben yazmak gibi "bilinmeyen bir kısıtlama karşılamak için başarısız değil" gibi bir mantık takip etmek için zorlu buluyorsanız, o zaman sadece SQL DDL ve SQL herhangi bir şey nullable sütunları kaçınarak tüm bu dağıtmak düşünün Boş değerler üreten DML (örn. Dış birleşimler)!


Dürüst olmak gerekirse, bu konuda söylenecek bir şey kaldığını düşünmüyordum. İlginç.
Jamie Ide

2
@Jamie Ide: Aslında, konuyla ilgili başka bir cevabım daha var: null'ların dahil edilmesi NOT IN (subquery)beklenmedik sonuçlar verebileceğinden, IN (subquery)tamamen kaçınmak ve her zaman kullanmaktan NOT EXISTS (subquery)( her zamanki gibi!) Her zaman null'ları doğru işlediği görülüyor. Ancak, NOT IN (subquery)beklenen sonucu verirken NOT EXISTS (subquery)beklenmedik sonuçlar verir! Konu ile ilgili notlarımı bulabilirsem bunu yazmaya başlayabilirim (sezgisel olmadığı için notlara ihtiyacım var!) Sonuç aynı olsa da: nulls önlemek!
oneday26

@oneday: NULL'ın tutarlı davranışa (dahili olarak tutarlı, spesifikasyonla tutarlı değil) sahip olması için özel kasalı olması gerektiği iddiasıyla karıştırdığımda. "Yalnızca belirtilen arama koşulu doğruysa bir tablo denetimi kısıtlaması sağlanmış" ifadesini okumak için 4.10 değerini değiştirmek yeterli olmaz mı?
DylanYoung

@DylanYoung: Hayır, Spec çok önemli bir nedenden dolayı bu şekilde ifade edilmiştir: Üç değer mantıktan SQL acılarını, bu değerlerdir TRUE, FALSEve UNKNOWN. 4.10 "Ben sadece bir tablonun her satırı için belirtilen arama koşulu DOĞRU veya BİLİNMİYOR ise bir tablo kontrol kısıtlaması yerine" okuyabildiğini varsayalım - cümlenin sonundaki değişikliği - atladığınız - - dan "için herhangi bir" "herkes için için 'Ben mantıksal değerleri yararlanmak için ihtiyacı hissediyorum çünkü anlamı 'gerçek' ve '' mutlaka klasik iki değerli mantık başvurmalıdır doğal dilde. yalancı
onedaywhen

1
Şunu düşünün: CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );- burada amaç, beşit aveya boş olmalıdır. Bir kısıtlamanın TRUE yerine getirilmesi gerekiyorsa, bu durumda null'ları açıkça işlemek için kısıtlamayı değiştirmemiz gerekir CHECK( a = b OR b IS NULL ). Böylece, her kısıtlama ...OR IS NULLdahil her boş sütun için kullanıcı tarafından mantık eklenmesi gerekir : daha karmaşıklık, bunu unuttum daha fazla hata, vb. Bu yüzden SQL standartları komitesi sadece pragmatik olmaya çalıştıklarını düşünüyorum.
oneday20

7

A'da, 3 setin her bir üyesine karşı eşitlik açısından test edilir (FALSE, FALSE, TRUE, UNKNOWN). Öğelerden biri DOĞRU olduğundan, koşul DOĞRU'dur. (Burada bazı kısa devrelerin olması da mümkündür, bu yüzden ilk DOĞRU'ya çarpar ve asla 3 = NULL değerini değerlendirmez durur.)

B'de durumu DEĞİL olarak değerlendirdiğini düşünüyorum (3 in (1,2, null)). Bilinmeyen olarak toplanan ayarlanan verim (FALSE, FALSE, UNKNOWN) ile eşitlik testi 3. NOT (BİLİNMİYOR) BİLİNMİYOR. Yani genel olarak, durumun gerçekliği bilinmemektedir, bu da sonunda esasen YANLIŞ olarak ele alınmaktadır.


7

Burada NOT IN (subquery)null'ları doğru bir şekilde ele almayan ve lehine kaçınılması gereken cevaplardan çıkarılabilir NOT EXISTS. Bununla birlikte, böyle bir sonuç erken olabilir. Aşağıdaki senaryoda, Chris Date (Veritabanı Programlama ve Tasarım, Cilt 2 No 9, Eylül 1989) olarak adlandırılan, NOT INnull'ları doğru şekilde işleyip doğru sonucu döndürmektir NOT EXISTS.

Miktar ( ) olarak parça ( ) sağladığı bilinen sptedarikçileri ( sno) temsil eden bir tablo düşünün . Tablo şu anda aşağıdaki değerleri içermektedir:pnoqty

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

Miktarın boş bırakılabilir olduğunu, yani bir tedarikçinin hangi miktarda bilinmese de parça tedarik ettiği bilindiğini kaydedebildiğini unutmayın.

Görev, tedarik parça numarası 'P1' olarak bilinen ancak 1000 miktarında olmayan tedarikçileri bulmaktır.

Aşağıdakiler NOT INyalnızca tedarikçi 'S2'yi doğru bir şekilde tanımlamak için kullanır :

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'
                       );

Bununla birlikte, aşağıdaki sorgu aynı genel yapıyı kullanır, ancak NOT EXISTSsonuçta 'S1' tedarikçisini içerir , ancak yanlıştır (yani miktarın boş olduğu):

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

Görünen NOT EXISTSgümüş mermi de öyle değil!

Tabii ki, sorunun kaynağı null'ların varlığıdır, bu nedenle 'gerçek' çözüm bu null'ları ortadan kaldırmaktır.

Bu, iki tablo kullanılarak (diğer olası tasarımların yanı sıra) elde edilebilir:

  • sp yedek parça tedarik ettiği bilinen tedarikçiler
  • spq bilinen miktarlarda parça tedarik ettiği bilinen tedarikçiler

spqreferansların muhtemelen yabancı bir anahtar kısıtlaması olması gerektiğine dikkat çeker sp.

Sonuç daha sonra 'eksi' ilişkisel operatör ( EXCEPTStandart SQL'deki anahtar kelime olarak) kullanılarak elde edilebilir;

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;

1
Aman Tanrım. Aslında bunu yazdığınız için teşekkür ederim .... bu beni deli ediyordu ..
Govind Rai

6

Null, veri anlamına gelir ve yokluğu, yani bilinmeyen, hiçbir şeyin veri değerini değil. Programlama geçmişinden gelen kişilerin bunu karıştırması çok kolaydır, çünkü işaretçiler kullanılırken C türü dillerde boş olan hiçbir şey yoktur.

Bu nedenle, ilk durumda 3 gerçekten (1,2,3, null) kümesinde bulunur, bu nedenle true döndürülür

Ancak ikincisinde

3'ün içinde olmadığı 'true' değerini seçin (null)

Böylece hiçbir şey döndürülmez, çünkü ayrıştırıcı, karşılaştırdığınız küme hakkında hiçbir şey bilmez - boş bir küme değil, bilinmeyen bir kümedir. (1, 2, null) kullanmak işe yaramaz çünkü (1,2) kümesi açıkça yanlıştır, ama sonra bilinmeyene karşı bunu yapıyorsunuz, bilinmeyen.


6

NULL'leri içeren bir alt sorgu için NOT IN ile filtrelemek istiyorsanız, null değil için kontrol edin

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )

Özel durumlarda herhangi bir kayıt döndürmeyen dış birleştirme sorgusu ile ilgili sorun vardı, Bu yüzden hem Null hem de varolan kayıtlar senaryosu için bu çözümü kontrol etti ve benim için çalıştı, Başka bir sorun ortaya çıktığında burada bahsedeceğim, Çok teşekkürler.
QMaster

1

bu Boy için:

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

ansi ayarlarından bağımsız olarak çalışır


orijinal soru için: B: 3'ün içinde olmadığı 'true' seçeneğini seçin (1, 2, null) null'ları kaldırmanın bir yolu yapılmalıdır, örneğin 3'ün içinde olmadığı 'true' seçeneğini seçin (1, 2, isnull (null, 0) ) genel mantık, NULL nedense, sorgunun bir adımında NULL değerlerini kaldırmanın bir yolunu bulun.

abc'den party_code'u burada party_code'un olmadığı bir yer olarak seçin (party_code'un null olmadığı xyz'den party_code'u seçin), ancak alanı unuttuysanız iyi şanslar null'lara izin verir, bu genellikle durumdur

1

SQL, doğruluk değerleri için üç değerli mantık kullanır. INSorgu beklenen sonucu üretir:

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

Ancak a eklenmesi NOTsonuçları tersine çevirmez:

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

Bunun nedeni yukarıdaki sorgunun aşağıdakine eşdeğer olmasıdır:

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

Where cümlesi şu şekilde değerlendirilir:

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

Dikkat:

  1. İlgili karşılaştırma NULLverimleriUNKNOWN
  2. ORİşlenen hiçbiri ekspresyonu TRUEve en az bir işlenen olan UNKNOWNverimleri UNKNOWN( ref )
  3. NOTBir UNKNOWNverim UNKNOWN( ref )

Yukarıdaki örneği ikiden fazla değere genişletebilirsiniz (örn. NULL, 1 ve 2), ancak sonuç aynı olacaktır: değerlerden biri NULLvarsa, hiçbir satır eşleşmez.


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.