Valori NULL all'interno della clausola NOT IN


245

Questo problema è emerso quando ho ottenuto diversi conteggi di record per quelle che pensavo fossero domande identiche una usando un not in wherevincolo e l'altra a left join. La tabella nel not invincolo aveva un valore nullo (dati errati) che faceva sì che quella query restituisse un conteggio di 0 record. In un certo senso capisco perché, ma potrei usare un po 'di aiuto per comprendere appieno il concetto.

Per dirlo semplicemente, perché la query A restituisce un risultato ma B no?

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

Questo era su SQL Server 2005. Ho anche scoperto che la chiamata set ansi_nulls offfa sì che B restituisca un risultato.

Risposte:


283

La query A è uguale a:

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

Poiché 3 = 3è vero, ottieni un risultato.

La query B è uguale a:

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

Quando ansi_nullsè 3 <> nullattivo , è SCONOSCIUTO, quindi il predicato restituisce SCONOSCIUTO e non si ottiene alcuna riga.

Quando ansi_nullsè spento, 3 <> nullè vero, quindi il predicato restituisce vero e ottieni una riga.


11
Qualcuno ha mai sottolineato che convertendo NOT INin una serie di <> andcambiamenti il ​​comportamento semantico di non in questo insieme in qualcos'altro?
Ian Boyd,

8
@Ian - Sembra che "A NOT IN ('X', 'Y')" sia in realtà un alias per A <> 'X' E A <> 'Y' in SQL. (Vedo che lo hai scoperto tu stesso in stackoverflow.com/questions/3924694/… , ma volevi assicurarti che la tua obiezione fosse affrontata in questa domanda.)
Ryan Olson,

Immagino che questo spieghi perché SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);produca una riga invece del gruppo di risultati vuoto che mi aspettavo.
binki,

2
Questo è un comportamento molto scarso del server SQL, perché se si aspetta il confronto NULL utilizzando "IS NULL", dovrebbe espandere la clausola IN sullo stesso comportamento e non applicare stupidamente la semantica sbagliata a se stessa.
OzrenTkalcecKrznaric,

@binki, la query viene eseguita se eseguita qui rextester.com/l/sql_server_online_compiler ma non funziona se eseguita qui sqlcourse.com/cgi-bin/interpreter.cgi .
Istiaque Ahmed,

53

Ogni volta che usi NULL hai davvero a che fare con una logica a tre valori.

La tua prima query restituisce risultati quando la clausola WHERE valuta:

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

Il secondo:

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

Lo SCONOSCIUTO non è lo stesso di FALSO, puoi facilmente testarlo chiamando:

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

Entrambe le query non daranno risultati

Se lo SCONOSCIUTO fosse lo stesso di FALSO, supponendo che la prima query avrebbe dato a FALSO il secondo avrebbe dovuto valutare su VERO in quanto sarebbe stato lo stesso di NOT (FALSO).
Questo non è il caso.

C'è un ottimo articolo su questo argomento su SqlServerCentral .

L'intero problema dei NULL e della logica a tre valori può inizialmente essere un po 'confuso, ma è essenziale capire per scrivere query corrette in TSQL

Un altro articolo che consiglierei è SQL Aggregate Functions e NULL .


33

NOT IN restituisce 0 record se confrontato con un valore sconosciuto

Poiché NULLè sconosciuto, una NOT INquery contenente a NULLo NULLnell'elenco dei possibili valori restituirà sempre i 0record poiché non è possibile accertarsi che il NULLvalore non sia il valore testato.


3
Questa è la risposta in breve. Ho trovato questo per essere più facile da capire anche senza alcun esempio.
Govind Rai,

18

Il confronto con null non è definito, a meno che non si usi IS NULL.

Pertanto, confrontando 3 con NULL (query A), restituisce un valore non definito.

Vale a dire SELEZIONA 'vero' dove 3 in (1,2, null) e SELEZIONA 'vero' dove 3 non in (1,2, null)

produrrà lo stesso risultato, poiché NOT (UNDEFINED) è ancora indefinito, ma non VERO


Ottimo punto selezionare 1 dove null in (null) non restituisce righe (ansi).
crokusek,

9

Il titolo di questa domanda al momento della stesura è

Vincolo SQL NON IN e valori NULL

Dal testo della domanda sembra che il problema si stesse verificando in una SELECTquery DML SQL , anziché in un DDL SQL CONSTRAINT.

Tuttavia, soprattutto in considerazione della formulazione del titolo, voglio sottolineare che alcune affermazioni fatte qui sono affermazioni potenzialmente fuorvianti, quelle sulla falsariga di (parafrasando)

Quando il predicato restituisce SCONOSCIUTO non si ottiene alcuna riga.

Anche se questo è il caso del DML SQL, quando si considerano i vincoli l'effetto è diverso.

Considera questa tabella molto semplice con due vincoli presi direttamente dai predicati nella domanda (e affrontati in una risposta eccellente da @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;

Secondo la risposta di @ Brannon, il primo vincolo (usando IN) viene valutato come VERO e il secondo vincolo (utilizzando NOT IN) viene valutato su SCONOSCIUTO. però , l'inserimento ha esito positivo! Pertanto, in questo caso non è strettamente corretto dire "non si ottiene alcuna riga" perché di conseguenza è stata inserita una riga.

L'effetto sopra è effettivamente quello corretto per quanto riguarda lo standard SQL-92. Confronta e confronta la seguente sezione dalle specifiche SQL-92

7.6 dove clausola

Il risultato di è una tabella di quelle righe di T per le quali il risultato della condizione di ricerca è vero.

4.10 Vincoli di integrità

Un vincolo di controllo tabella è soddisfatto se e solo se la condizione di ricerca specificata non è falsa per una riga di una tabella.

In altre parole:

Nel DML SQL, le righe vengono rimosse dal risultato quando viene WHEREvalutato UNKNOWN perché non lo è soddisfa la condizione "è vero".

In SQL DDL (cioè vincoli), righe non vengono rimossi dal risultato quando restituiscono UNKNOWN perché non soddisfa la condizione "non è falsa".

Sebbene gli effetti nel DML SQL e nel DDL SQL rispettivamente possano sembrare contraddittori, esiste una ragione pratica per dare ai risultati SCONOSCIUTI il "vantaggio del dubbio" consentendo loro di soddisfare un vincolo (più correttamente, permettendo loro di non riuscire a soddisfare un vincolo) : senza questo comportamento, ogni vincolo dovrebbe gestire esplicitamente i null e sarebbe molto insoddisfacente dal punto di vista del design del linguaggio (per non parlare del dolore giusto per i programmatori!)

ps se trovi difficile seguire una logica come "sconosciuto non riesce a soddisfare un vincolo" come sto per scriverlo, allora considera che puoi fare a meno di tutto questo semplicemente evitando colonne nullable in SQL DDL e qualsiasi cosa in SQL DML che produce valori null (ad es. Join esterni)!


Onestamente non pensavo che ci fosse rimasto altro da dire su questo argomento. Interessante.
Jamie Ide,

2
@Jamie Ide: In realtà, ho un'altra risposta sull'argomento: poiché NOT IN (subquery)coinvolgere i null può dare risultati inaspettati, è allettante evitarlo IN (subquery)completamente e usarlo sempre NOT EXISTS (subquery)(come ho fatto una volta!) Perché sembra che gestisca sempre correttamente i null. Tuttavia, ci sono casi in cui NOT IN (subquery)dà il risultato atteso mentre NOT EXISTS (subquery)dà risultati inaspettati! Potrei ancora scriverlo se riesco a trovare le mie note sull'argomento (ho bisogno di note perché non è intuitivo!) La conclusione è la stessa, però: evita i null!
Onedayquando il

@onedayquando sono confuso dalla tua affermazione che NULL avrebbe bisogno di un case speciale per avere un comportamento coerente (internamente coerente, non coerente con le specifiche). Non sarebbe sufficiente modificare 4.10 per leggere "Un vincolo di controllo tabella è soddisfatto se e solo se la condizione di ricerca specificata è vera"?
DylanYoung,

@DylanYoung: No, la specifica è formulata in questo modo per una ragione fondamentale: soffre SQL da tre logica valore, in cui tali valori sono TRUE, FALSEe UNKNOWN. Suppongo che 4.10 avrebbe potuto leggere "Un vincolo di controllo della tabella è soddisfatto se e solo se la condizione di ricerca specificata è VERA o SCONOSCIUTA per ogni riga di una tabella" - nota la mia modifica alla fine della frase - che hai omesso - - da "per qualsiasi" a "per tutti". Sento la necessità di capitalizzare i valori logici perché il significato di "vero" e "falso" nel linguaggio naturale deve sicuramente riferirsi alla logica classica a due valori.
onedaywhen

1
Considera: CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );- l'intento qui è che bdeve essere uguale ao nullo. Se un vincolo dovesse risultare VERO per essere soddisfatto, avremmo bisogno di modificarlo per gestire esplicitamente i null, ad es CHECK( a = b OR b IS NULL ). Pertanto, ogni vincolo dovrebbe avere la ...OR IS NULLlogica aggiunta dall'utente per ogni colonna nullable coinvolta: più complessità, più bug quando si sono dimenticati di farlo, ecc. Quindi penso che il comitato degli standard SQL abbia semplicemente cercato di essere pragmatico.
Onedayquando il

7

In A, 3 viene testato per l'uguaglianza nei confronti di ciascun membro dell'insieme, cedendo (FALSO, FALSO, VERO, SCONOSCIUTO). Poiché uno degli elementi è VERO, la condizione è VERA. (È anche possibile che si verifichino dei cortocircuiti qui, quindi si interrompe non appena raggiunge il primo VERO e non valuta mai 3 = NULL.)

In B, penso che stia valutando la condizione come NOT (3 in (1,2, null)). Test 3 per l'uguaglianza rispetto ai rendimenti impostati (FALSO, FALSO, SCONOSCIUTO), che è aggregato a SCONOSCIUTO. NOT (SCONOSCIUTO) restituisce SCONOSCIUTO. Quindi nel complesso la verità della condizione è sconosciuta, che alla fine viene essenzialmente trattata come FALSO.


7

Si può concludere dalle risposte qui che NOT IN (subquery)non gestiscono correttamente i null e dovrebbero essere evitate a favore NOT EXISTS. Tuttavia, tale conclusione può essere prematura. Nel seguente scenario, accreditato a Chris Date (Programmazione e progettazione del database, volume 2 n. 9, settembre 1989), è NOT INche gestisce correttamente i null e restituisce il risultato corretto, anziché NOT EXISTS.

Si consideri una tabella spper rappresentare i fornitori ( sno) che sono noti per fornire parti ( pno) in quantità ( qty). La tabella contiene attualmente i seguenti valori:

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

Si noti che la quantità è nullable cioè per essere in grado di registrare il fatto che un fornitore è noto per fornire parti anche se non è noto in quale quantità.

Il compito è quello di trovare i fornitori che sono noti fornire il numero di parte 'P1' ma non in quantità di 1000.

I seguenti usi NOT INper identificare correttamente solo il fornitore 'S2':

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

Tuttavia, la query seguente utilizza la stessa struttura generale ma con NOT EXISTSil risultato include erroneamente il fornitore "S1" nel risultato (ovvero per cui la quantità è nulla):

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

Quindi NOT EXISTSnon è il proiettile d'argento che potrebbe essere apparso!

Naturalmente, la fonte del problema è la presenza di null, quindi la soluzione "reale" è quella di eliminare quei null.

Ciò può essere ottenuto (tra gli altri possibili progetti) utilizzando due tabelle:

  • sp fornitori noti per la fornitura di parti
  • spq fornitori noti per la fornitura di parti in quantità note

notando che probabilmente ci dovrebbe essere un vincolo di chiave esterna in cui i spqriferimenti sp.

Il risultato può quindi essere ottenuto usando l'operatore relazionale 'meno' (essendo la EXCEPTparola chiave in SQL standard) ad es

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
Oh mio Dio. Grazie per aver effettivamente scritto questo .... questo mi stava facendo impazzire ..
Govind Rai

6

Null significa e assenza di dati, ovvero è sconosciuto, non un valore di dati di nulla. È molto facile per le persone provenienti da un background di programmazione confonderlo perché nei linguaggi di tipo C quando si usano i puntatori null è davvero nulla.

Quindi nel primo caso 3 è effettivamente nell'insieme di (1,2,3, null), quindi viene restituito true

Nel secondo invece puoi ridurlo a

seleziona 'true' dove 3 non è (null)

Quindi non viene restituito nulla perché il parser non sa nulla del set con cui lo si sta confrontando - non è un set vuoto ma un set sconosciuto. Usare (1, 2, null) non aiuta perché il set (1,2) è ovviamente falso, ma poi lo stai facendo contro lo sconosciuto, che è sconosciuto.


6

Se si desidera filtrare con NOT IN per una sottoquery che contiene NULL, selezionare per non null

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

Ho avuto problemi con la query di join esterna che non ha restituito alcun record in situazioni speciali, quindi ho controllato questa soluzione sia per lo scenario Null che per quello esistente e ha funzionato per me. Se si sono verificati altri problemi, citerò qui, grazie mille.
QMaster

1

questo è per Boy:

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

funziona indipendentemente dalle impostazioni ANSI


per la domanda originale: B: seleziona 'true' dove 3 non in (1, 2, null) deve essere fatto un modo per rimuovere i null ad es. seleziona 'true' dove 3 non in (1, 2, isnull (null, 0) ) la logica generale è, se NULL è la causa, quindi trovare un modo per rimuovere i valori NULL in qualche passaggio della query.

seleziona party_code da abc come dove party_code non è presente (seleziona party_code da xyz dove party_code non è null) ma buona fortuna se hai dimenticato il campo consente valori null, come spesso accade

1

SQL utilizza una logica a tre valori per i valori di verità. La INquery produce il risultato previsto:

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

Ma l'aggiunta di a NOTnon inverte i risultati:

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

Questo perché la query sopra è equivalente alla seguente:

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

Ecco come viene valutata la clausola where:

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

Notare che:

  1. Il confronto con i NULLrendimentiUNKNOWN
  2. L' ORespressione in cui nessuno degli operandi è TRUEe almeno un operando è UNKNOWNyield UNKNOWN( ref )
  3. The NOTof UNKNOWNyields UNKNOWN( ref )

Puoi estendere l'esempio sopra a più di due valori (es. NULL, 1 e 2) ma il risultato sarà lo stesso: se uno dei valori è NULLallora nessuna riga corrisponderà.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.