Oracle
 sql >> Base de Dados >  >> RDS >> Oracle

Seleção NOT IN com valores NULL


Primeiro um pouco de teoria:Nulo (SQL)

As partes mais importantes para nós do link acima:

Comparações com NULL e a lógica de três valores (3VL)

Como Null não é membro de nenhum domínio de dados, ele não é considerado um "valor", mas sim um marcador (ou espaço reservado) indicando a ausência de valor. Por causa disso, comparações com Null nunca podem resultar em True ou False, mas sempre em um terceiro resultado lógico, Unknown.[8] O resultado lógico da expressão abaixo, que compara o valor 10 com Null, é Unknown:
SELECT 10 = NULL       -- Results in Unknown

para que ambas as comparações:x = NULL e x <> NULL avalia como NULL(desconhecido).

O SQL implementa três resultados lógicos, portanto, as implementações do SQL devem fornecer uma lógica especializada de três valores (3VL). As regras que governam a lógica de três valores do SQL são mostradas nas tabelas abaixo (p eq representam estados lógicos)"[9] As tabelas de verdade que o SQL usa para AND, OR e NOT correspondem a um fragmento comum da lógica de três valores de Kleene e Łukasiewicz ( que diferem em sua definição de implicação, no entanto, o SQL não define tal operação).
+---------+-------------+-------------+-------------+-----------+--------+
|    p    |        q    |     p OR q  |     p AND q |    p = q  |p != q  |
+---------+-------------+-------------+-------------+-----------+--------+
| True    |     True    |     True    |     True    |   True    | False  |
| True    |     False   |     True    |     False   |   False   | True   |
| True    |     Unknown |     True    |     Unknown |   Unknown | Unknown|
| False   |     True    |     True    |     False   |   False   | True   |
| False   |     False   |     False   |     False   |   True    | False  |
| False   |     Unknown |     Unknown |     False   |   Unknown | Unknown|
| Unknown |     True    |     True    |     Unknown |   Unknown | Unknown|
| Unknown |     False   |     Unknown |     False   |   Unknown | Unknown|
| Unknown |     Unknown |     Unknown |     Unknown |   Unknown | Unknown|
+---------+-------------+-------------+-------------+-----------+--------+

Efeito de Desconhecido nas cláusulas WHERE

A lógica de três valores SQL é encontrada em Data Manipulation Language (DML) em predicados de comparação de instruções e consultas DML. A cláusula WHERE faz com que a instrução DML atue apenas nas linhas para as quais o predicado é avaliado como True.

Então, em resumo:a cláusula WHERE trata NULL como FALSE

Agora, considere um caso mais simples:
SELECT * FROM T1;

|      X |
|--------|
|      1 |
| (null) |

e uma consulta:
SELECT * FROM t1 WHERE x IN (1, NULL);

A consulta acima é um atalho para esta:
SELECT * FROM t1 
WHERE x = 1
  OR  x = NULL

Para a segunda linha da tabela t ( x =NULL) esta condição se parece com:
WHERE NULL = 1
   OR NULL = NULL

então esta condição para a linha x=NULL é avaliado como NULL porque NULL=1 é NULL, NULL=NULL é NULL e NULL OR NULL também é NULL (veja a tabela 3VL acima).

Agora considere um caso mais curioso:
SELECT * FROM t1 WHERE x NOT IN (1, NULL);

Esta cláusula x NOT IN (1, NULL) é equivalente a NOT ( x IN (1, NULL) )
então também é equivalente a:
NOT (
  x = 1
  OR
  x = NULL
)

e de acordo com as leis de De Morgan é equivalente a:
NOT ( x = 1 ) AND NOT ( x = NULL )

e (se substituirmos NOT x = y com x <> y ) também é equivalente a:
 x <> 1 AND x <> NULL

Observe atentamente a última condição:
WHERE 
x <> 1 AND x <> NULL

Sabemos que x <> NULL sempre avalia como NULL. Também sabemos da tabela 3VL acima, que tanto true AND NULL é NULL e false AND NULL é avaliada como FALSE, portanto, toda a condição sempre é avaliada como FALSE ou NULL, mas nunca é avaliada como TRUE.

Portanto, uma consulta com esta condição:
SELECT .....
WHERE x NOT IN ( NULL, whatever)

sempre retorna um conjunto de resultados vazio

E agora sua consulta, que também é curiosa:
SELECT * FROM t1
WHERE (id, val) NOT IN (select id, val from data2);

que pode ser reescrita (usando valores constantes) para:
SELECT * FROM t1
WHERE (id, val) NOT IN (
       (1, null),
       (2, 2 )
)

Esta consulta está usando a chamada expressão de valor de linha

Basicamente uma condição usando o valor de linha expresso assim
(a, b) = (x, y)

é equivalente a este:
a = x AND b = y

então a consulta acima pode ser reescrita nesta:
SELECT * FROM t1
WHERE NOT (
   id = 1 AND val = NULL
   OR
   id = 2 AND val = 2
)

De acordo com as leis de De Morgan, isso é idêntico a:
SELECT * FROM t1
WHERE 
   NOT ( id = 1 AND val = NULL )
   AND
   NOT ( id = 2 AND val = 2 )

e ainda para:
SELECT * FROM t1
WHERE 
   ( id <> 1 OR val <> NULL )
   AND
   ( id <> 2 OR val <> 2 )

Desde a primeira parte ( id <> 1 OR val <> NULL ) da condição é avaliada como verdadeira somente no caso em que id <> 1 (veja a tabela 3VL acima), esta condição pode ser simplificada em:
SELECT * FROM t1
WHERE 
   ( id <> 1 )
   AND
   ( id <> 2 OR val <> 2 )

e ainda (de acordo com as leis de De Morgan) em:
SELECT * FROM t1
WHERE 
   id <> 1 AND id <> 2
   OR
   id <> 1 AND  val <> 2

então nem (1,1) nem (2,2) da fonte data1 cumprir estas condições.