SQL "seleziona dove non in subquery" non restituisce risultati


130

Disclaimer: ho capito il problema (credo), ma volevo aggiungere questo problema a StackTranslate.it poiché non riuscivo (facilmente) a trovarlo da nessuna parte. Inoltre, qualcuno potrebbe avere una risposta migliore di me.

Ho un database in cui una tabella "Comune" fa riferimento a diverse altre tabelle. Volevo vedere quali record nella tabella comune erano orfani (ovvero, non aveva riferimenti da nessuna delle altre tabelle).

Ho eseguito questa query:

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

So che ci sono record orfani, ma nessun record è stato restituito. Perchè no?

(Questo è SQL Server, se è importante.)


Questo stackoverflow.com/a/129152/1667619 risponde abbastanza bene alla domanda PERCHÉ.
Ruchan,

Risposte:


234

Aggiornare:

Questi articoli nel mio blog descrivono le differenze tra i metodi in modo più dettagliato:


Esistono tre modi per eseguire tale query:

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

Quando table1.common_idnon è nullable, tutte queste query sono semanticamente uguali.

Quando è nullable, NOT INè diverso, poiché IN(e, quindi, NOT IN) restituisce NULLquando un valore non corrisponde a nulla in un elenco contenente a NULL.

Questo può essere fonte di confusione ma può diventare più ovvio se ricordiamo la sintassi alternativa per questo:

common_id = ANY
(
SELECT  common_id
FROM    table1 t1
)

Il risultato di questa condizione è un prodotto booleano di tutti i confronti all'interno dell'elenco. Naturalmente, un singolo NULLvalore produce il NULLrisultato che rende anche l'intero risultato NULL.

Non possiamo mai dire con certezza che common_idnon è uguale a niente da questo elenco, poiché almeno uno dei valori è NULL.

Supponiamo di avere questi dati:

common

--
1
3

table1

--
NULL
1
2

LEFT JOIN / IS NULLe NOT EXISTSritornerà 3, NOT INnon restituirà nulla (dal momento che valuterà sempre a FALSEo NULL).

In MySQL, nel caso di colonne non annullabili, LEFT JOIN / IS NULLe NOT INsono un po '(diversi percento) più efficienti di NOT EXISTS. Se la colonna è nullable, NOT EXISTSè la più efficiente (di nuovo, non molto).

In Oracle, tutte e tre le query producono gli stessi piani (an ANTI JOIN).

In SQL Server, NOT IN/ NOT EXISTSsono più efficienti, dal momento che LEFT JOIN / IS NULLnon può essere ottimizzato per un ANTI JOINdal suo ottimizzatore.

In PostgreSQL, LEFT JOIN / IS NULLe NOT EXISTSsono più efficienti di NOT IN, sinusoidali sono ottimizzati per un Anti Join, mentre NOT INusi hashed subplan(o anche un semplice subplanse la subquery è troppo grande per l'hash)


8
Bella risposta! Grazie!
StevenMcD,

questo è fantastico e molto utile
kavun

1
+1 perché, a distanza di quattro anni e mezzo, questa risposta mi ha aiutato con un problema che mi aveva lasciato perplesso!
Carson63000,

@ Carson63000 Snap! Pensavo di impazzire prima di vedere questa risposta
Bobby,

1
@IstiaqueAhmed: restituisce NOT EXISTSTRUE se la query al suo interno restituisce delle righe. SELECT NULLpotrebbe anche essere SELECT *o SELECT 1qualsiasi altra cosa, il NOT EXISTSpredicato non esamina i valori delle righe, ma li conta.
Quassnoi,

36

Se vuoi che il mondo sia un luogo booleano a due valori, devi evitare tu stesso il caso nullo (terzo valore).

Non scrivere clausole IN che consentano null nel lato elenco. Filtrali!

common_id not in
(
  select common_id from Table1
  where common_id is not null
)

6
i null nell'elenco in-clause sono un motivo comune per i risultati delle query mancanti.
Amy B,

'Quando si confronta con un valore nullo, la risposta è sconosciuta' - dalla risposta di @Jeremy Stein. Da common_id not in, possiamo ancora avere common_idvalore NULL. Quindi il problema di non ottenere risultati persiste?
Istiaque Ahmed,

5

Table1 o Table2 ha alcuni valori null per common_id. Utilizzare invece questa query:

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)

1
Cosa succede se ci sono dati in una tabella ma non nell'altra? Vuoi "e" o "o" lì?
Philip Kelley,

1
Sto cercando record non referenziati in nessuna tabella, quindi voglio AND. Chiarirò la domanda.
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)

4

Appena al di sopra della mia testa ...

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

Ho eseguito alcuni test ed ecco i miei risultati con la risposta di @ patmortech e i commenti di @ rexem.

Se Table1 o Table2 non sono indicizzati su commonID, si ottiene una scansione della tabella ma la query di @ patmortech è ancora due volte più veloce (per una tabella principale di 100K righe).

Se nessuno dei due è indicizzato su commonID, si ottengono due scansioni di tabelle e la differenza è trascurabile.

Se entrambi sono indicizzati su commonID, la query "non esiste" viene eseguita in 1/3 del tempo.


1
Dovrebbe essere un AND nella clausola where. Altrimenti, funziona.
Jeremy Stein,

1
modificato per il tuo commento. "O" seleziona gli orfani in entrambe le tabelle.
Austin Salonen,

1
Così va meglio. A proposito, c'è qualche motivo per cui dovrei usare i join esterni piuttosto che la subquery?
Jeremy Stein,

3
La leggibilità è primaria. Sospetto che verrebbe generato un piano di esecuzione migliore, ma senza un piano di query non posso confermare.
Austin Salonen,

2
Questo approccio è peggio che usando NOT EXISTS: il join comporta il recupero di più righe di quelle necessarie, quindi i risultati confrontati per le colonne sono nulli. E NOT EXISTS è più leggibile per l'avvio.
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

1
Questo approccio è peggio che usando NOT EXISTS: il join comporta il recupero di più righe di quelle necessarie, quindi i risultati confrontati per le colonne sono nulli. Funziona, ma le prestazioni non saranno così buone - forse peggio che usare IN con subquery correlate.
OMG Ponies,

3

Supponiamo che questi valori per common_id:

Common - 1
Table1 - 2
Table2 - 3, null

Vogliamo che la riga in Common ritorni, perché non esiste in nessuna delle altre tabelle. Tuttavia, il null getta in una chiave inglese.

Con questi valori, la query è equivalente a:

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

Ciò equivale a:

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

Questo è dove inizia il problema. Quando si confronta con un null, la risposta è sconosciuta . Quindi la query si riduce a

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

falso o sconosciuto è sconosciuto:

select *
from Common
where true
and not (unknown)

vero e non sconosciuto è anche sconosciuto:

select *
from Common
where unknown

La condizione where non restituisce record in cui il risultato non è noto, quindi non otteniamo alcun record.

Un modo per gestirlo è utilizzare l'operatore esistente anziché in. Gli esistenti non restituiscono mai sconosciuti perché operano su righe anziché su colonne. (Una riga esiste o non esiste; nessuna di questa ambiguità nulla a livello di riga!)

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)

2

questo ha funzionato per me :)

selezionare * da Comune

dove

common_id non in (selezionare ISNULL (common_id, 'dummy-data') da Table1)

e common_id non in (selezionare ISNULL (common_id, 'dummy-data') da Table2)


@marlar, le sottoquery restituiscono sempre 1 o 0, non un elenco di valori. Quindi come si NOT INesibiranno lì?
Istiaque Ahmed,

0
select *,
(select COUNT(ID)  from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun 
from CategoryMaster

0

Ho avuto un esempio in cui stavo guardando in alto e poiché una tabella conteneva il valore come doppio, l'altra come stringa, non corrispondevano (o non corrispondevano senza un cast). Ma solo NON IN . Come SELECT ... IN ... ha funzionato. Strano, ma ho pensato di condividere nel caso qualcuno incontrasse questa semplice soluzione.


0

Seguire l'esempio seguente per comprendere l'argomento sopra:

Inoltre puoi visitare il seguente link per conoscere Anti join

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

Ma se usiamo NOT INin quel caso non otteniamo alcun dato.

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

nessun dato trovato

Ciò sta accadendo poiché ( select department_id from hr.employees) restituisce un valore null e l'intera query viene valutata come falsa. Possiamo vederlo se cambiamo leggermente l'SQL come di seguito e gestiamo valori null con la funzione NVL.

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

Ora stiamo ottenendo dati:

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

Anche in questo caso stiamo ottenendo dati poiché abbiamo gestito il valore null con la funzione NVL.


I risultati di SQl non vengono visualizzati in forma tabulare, per favore spogliati con me.
Rajesh Sarkar,
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.