Valores NULL dentro da cláusula NOT IN

244

Esse problema surgiu quando obtive diferentes contagens de registros para o que eu pensava serem consultas idênticas, uma usando uma not in whererestrição e a outra a left join. A tabela na not inrestrição tinha um valor nulo (dados inválidos) que fazia com que a consulta retornasse uma contagem de 0 registros. Eu meio que entendo o porquê, mas eu poderia usar alguma ajuda para entender completamente o conceito.

Para simplificar, por que a consulta A retorna um resultado, mas B não?

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

Isso foi no SQL Server 2005. Também descobri que a chamada set ansi_nulls offfaz com que B retorne um resultado.

Jamie Ide
fonte

Respostas:

283

A consulta A é igual a:

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

Como 3 = 3é verdade, você obtém um resultado.

A consulta B é igual a:

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

Quando ansi_nullsestá 3 <> nullativado , é DESCONHECIDO, portanto, o predicado é avaliado como DESCONHECIDO, e você não recebe nenhuma linha.

Quando ansi_nullsdesativado, 3 <> nullé verdadeiro; portanto, o predicado é avaliado como verdadeiro e você obtém uma linha.

Brannon
fonte
11
Alguém já apontou que a conversão NOT INpara uma série de <> andmudanças altera o comportamento semântico de não neste conjunto para outra coisa?
Ian Boyd
8
@Ian - Parece que "A NOT IN ('X', 'Y')" é na verdade um apelido para A <> 'X' AND A <> 'Y' no SQL. (Eu vejo que você descobriu isso sozinho em stackoverflow.com/questions/3924694/... , mas queria ter certeza de sua objeção foi abordada nesta pergunta.)
Ryan Olson
Acho que isso explica por que SELECT 1 WHERE NULL NOT IN (SELECT 1 WHERE 1=0);gera uma linha em vez do conjunto de resultados vazio que eu esperava.
binki
2
Esse é um comportamento muito ruim do servidor SQL, porque se ele espera uma comparação NULL usando "IS NULL", deve expandir a cláusula IN para esse mesmo comportamento e não aplicar estupidamente a semântica errada.
OzrenTkalcecKrznaric
@binki, sua consulta é executada se for executada aqui rextester.com/l/sql_server_online_compiler, mas não funcionará se for executada aqui sqlcourse.com/cgi-bin/interpreter.cgi .
Istiaque Ahmed
52

Sempre que você usa NULL, está realmente lidando com uma lógica de três valores.

Sua primeira consulta retorna resultados à medida que a cláusula WHERE é avaliada para:

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

O segundo:

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

O DESCONHECIDO não é o mesmo que FALSO, você pode testá-lo facilmente chamando:

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

Ambas as consultas não fornecerão resultados

Se UNKNOWN fosse o mesmo que FALSE, assumindo que a primeira consulta lhe forneceria FALSE, a segunda teria que ser avaliada como TRUE, pois teria sido a mesma que NOT (FALSE).
Esse não é o caso.

Há um artigo muito bom sobre esse assunto no SqlServerCentral .

Toda a questão dos NULLs e da lógica de três valores pode ser um pouco confusa no começo, mas é essencial entender para escrever consultas corretas no TSQL

Outro artigo que eu recomendaria é SQL Aggregate Functions e NULL .

Kristof
fonte
33

NOT IN retorna 0 registros quando comparado a um valor desconhecido

Como NULLé desconhecido, uma NOT INconsulta que contenha a NULLou NULLs na lista de valores possíveis sempre retornará 0registros, pois não há como garantir que o NULLvalor não seja o valor que está sendo testado.

YonahW
fonte
3
Esta é a resposta em poucas palavras. Achei isso mais fácil de entender, mesmo sem nenhum exemplo.
Govind Rai
18

Comparar com nulo é indefinido, a menos que você use IS NULL.

Portanto, ao comparar 3 com NULL (consulta A), ele retorna indefinido.

Ou seja, SELECT 'true' em que 3 pol (1,2, nulo) e SELECT 'true' em que 3 pol (1,2, nulo)

produzirá o mesmo resultado, pois NOT (UNDEFINED) ainda está indefinido, mas não é TRUE

Sunny Milenov
fonte
Ótimo ponto. selecione 1 onde nulo em (nulo) não retorna linhas (ansi).
22412 crokusek
9

O título desta pergunta no momento da redação deste artigo é

Restrição SQL NOT IN e valores NULL

A partir do texto da pergunta, parece que o problema estava ocorrendo em uma SELECTconsulta SQL DML , em vez de uma DDL SQL CONSTRAINT.

No entanto, especialmente dada a redação do título, quero salientar que algumas declarações feitas aqui são declarações potencialmente enganosas, aquelas do tipo (parafraseando)

Quando o predicado é avaliado como DESCONHECIDO, você não recebe nenhuma linha.

Embora este seja o caso do SQL DML, ao considerar restrições, o efeito é diferente.

Considere esta tabela muito simples com duas restrições tiradas diretamente dos predicados da pergunta (e abordadas em uma excelente resposta de @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;

De acordo com a resposta de @ Brannon, a primeira restrição (usando IN) é avaliada como VERDADEIRA e a segunda restrição (usando NOT IN) é avaliada como DESCONHECIDO. Contudo , a inserção foi bem-sucedida! Portanto, neste caso, não é estritamente correto dizer "você não recebe nenhuma linha" porque, de fato, temos uma linha inserida como resultado.

O efeito acima é de fato o correto em relação ao padrão SQL-92. Compare e contraste a seção a seguir da especificação SQL-92

7.6 cláusula where

O resultado de é uma tabela daquelas linhas de T para as quais o resultado da condição de pesquisa é verdadeiro.

4.10 Restrições de integridade

Uma restrição de verificação de tabela é atendida se, e somente se, a condição de pesquisa especificada não for falsa para qualquer linha de uma tabela.

Em outras palavras:

No SQL DML, as linhas são removidas do resultado quando WHEREavalia como DESCONHECIDO, porque não atende à condição "é verdadeira".

Em SQL DDL (ou seja, restrições), as linhas não são removidos a partir do resultado quando avaliam a desconhecida, dado que não satisfazem a condição "não é falsa".

Embora os efeitos no SQL DML e no SQL DDL possam parecer contraditórios, existe uma razão prática para dar aos resultados UNKNOWN o 'benefício da dúvida', permitindo que eles satisfaçam uma restrição (mais corretamente, permitindo que eles não deixem de satisfazer uma restrição). : sem esse comportamento, todas as restrições teriam que lidar explicitamente com nulos e isso seria muito insatisfatório do ponto de vista do design da linguagem (para não mencionar, um problema para os codificadores!)

ps Se você está achando tão desafiador seguir uma lógica como "desconhecido não falha em satisfazer uma restrição" como eu estou escrevendo, considere que você pode dispensar tudo isso simplesmente evitando colunas anuláveis ​​no DDL do SQL e qualquer coisa no SQL DML que produz nulos (por exemplo, junções externas)!

um dia quando
fonte
Sinceramente, acho que não havia mais nada a dizer sobre esse assunto. Interessante.
21711 Jamie Ide
2
@ Jamie Ide: Na verdade, tenho outra resposta sobre o assunto: porque o NOT IN (subquery)nulo pode dar resultados inesperados, é tentador evitar IN (subquery)completamente e sempre usar NOT EXISTS (subquery)(como eu fiz uma vez!), Porque parece que ele sempre lida com o nulo corretamente. No entanto, existem casos em que NOT IN (subquery)fornece o resultado esperado, enquanto NOT EXISTS (subquery)fornece resultados inesperados! Ainda posso escrever isso se conseguir encontrar minhas anotações sobre o assunto (preciso de anotações porque não é intuitivo!) A conclusão é a mesma: evite valores nulos!
precisa saber é o seguinte
@onedaywhen estou confuso com a sua afirmação de que NULL precisaria ser especial para ter um comportamento consistente (internamente consistente, não consistente com as especificações). Não seria suficiente alterar 4.10 para ler "Uma restrição de verificação de tabela é satisfeita se e somente se a condição de pesquisa especificada for verdadeira"?
precisa saber é o seguinte
@DylanYoung: Não, a especificação está assim escrita por uma razão crucial: o SQL sofre de três lógicas de valores, onde estão esses valores TRUE, FALSEe UNKNOWN. Suponho que a versão 4.10 poderia ter lido: "Uma restrição de verificação de tabela é satisfeita se, e somente se, a condição de pesquisa especificada for VERDADEIRA ou DESCONHECIDA para cada linha de uma tabela" - observe minha alteração no final da frase - que você omitiu - - de "for any" para "for all". Sinto a necessidade de capitalizar os valores lógicos porque o significado de 'verdadeiro' e 'falso' na linguagem natural deve certamente se referir à lógica clássica de dois valores.
onedaywhen
1
Considere: CREATE TABLE T ( a INT NOT NULL UNIQUE, b INT CHECK( a = b ) );- a intenção aqui é que bdeve ser igual aou ser nulo. Se uma restrição tiver que resultar em TRUE para ser satisfeita, precisaremos alterar a restrição para lidar com nulos explicitamente, por exemplo CHECK( a = b OR b IS NULL ). Assim, toda restrição precisaria ter ...OR IS NULLlógica adicionada pelo usuário para cada coluna anulável envolvida: mais complexidade, mais bugs quando eles se esquecessem de fazê-lo, etc. Então, acho que o comitê de padrões SQL estava apenas tentando ser pragmático.
precisa saber é o seguinte
7

Em A, 3 é testado quanto à igualdade em relação a cada membro do conjunto, produzindo (FALSE, FALSE, TRUE, UNKNOWN). Como um dos elementos é VERDADEIRO, a condição é VERDADEIRA. (Também é possível que ocorra um curto-circuito aqui, portanto, ele pára assim que atinge o primeiro TRUE e nunca avalia 3 = NULL.)

Em B, acho que está avaliando a condição como NOT (3 in (1,2, null)). Teste 3 para igualdade em relação aos rendimentos definidos (FALSE, FALSE, UNKNOWN), que é agregado a UNKNOWN. NOT (DESCONHECIDO) gera DESCONHECIDO. Portanto, no geral, a verdade da condição é desconhecida, que no final é essencialmente tratada como FALSA.

Dave Costa
fonte
7

Pode-se concluir a partir de respostas aqui que NOT IN (subquery)não tratam nulos corretamente e devem ser evitadas em favor de NOT EXISTS. No entanto, essa conclusão pode ser prematura. No cenário a seguir, creditado a Chris Date (Database Programming and Design, Vol. 2, n. 9, setembro de 1989), é NOT INque lida com nulos corretamente e retorna o resultado correto, em vez deNOT EXISTS .

Considere uma tabela sppara representar os fornecedores ( sno) que são conhecidos por fornecer peças ( pno) em quantidade ( qty). A tabela atualmente possui os seguintes valores:

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

Observe que a quantidade é anulável, ou seja, para poder registrar o fato de que um fornecedor é conhecido por fornecer peças, mesmo que não seja conhecido em qual quantidade.

A tarefa é encontrar os fornecedores que sabem o número da peça de fornecimento 'P1', mas não em quantidades de 1000.

Os seguintes usos usam NOT INpara identificar corretamente apenas o fornecedor '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'
                       );

No entanto, a consulta abaixo usa a mesma estrutura geral, mas com, NOT EXISTSmas inclui incorretamente o fornecedor 'S1' no resultado (ou seja, para o qual a quantidade é nula):

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

Portanto, NOT EXISTSnão é a bala de prata que pode ter aparecido!

Obviamente, a fonte do problema é a presença de nulos; portanto, a solução 'real' é eliminar esses nulos.

Isso pode ser alcançado (entre outros projetos possíveis) usando duas tabelas:

  • sp fornecedores conhecidos por fornecer peças
  • spq fornecedores conhecidos por fornecer peças em quantidades conhecidas

observando que provavelmente deve haver uma restrição de chave estrangeira em que as spqreferênciassp .

O resultado pode ser obtido usando o operador relacional 'menos' (sendo a EXCEPTpalavra - chave no SQL padrão), por exemplo

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;
um dia quando
fonte
1
AMD. Obrigado por realmente escrever isso .... isso estava me deixando louca ..
Govind Rai
6

Nulo significa e ausência de dados, isto é, é desconhecido, não um valor de dados de nada. É muito fácil para as pessoas com experiência em programação confundirem isso, porque nas linguagens do tipo C, ao usar ponteiros nulos, de fato, nada é.

Portanto, no primeiro caso, 3 está realmente no conjunto de (1,2,3, nulo), então true é retornado

No segundo, você pode reduzi-lo para

selecione 'true' onde 3 não estão em (nulo)

Portanto, nada é retornado porque o analisador não sabe nada sobre o conjunto com o qual você está comparando - não é um conjunto vazio, mas um conjunto desconhecido. Usar (1, 2, nulo) não ajuda porque o conjunto (1,2) é obviamente falso, mas você está fazendo isso contra desconhecido, o que é desconhecido.

Cruachan
fonte
6

Se você deseja filtrar com NOT IN para uma subconsulta contendo NULLs, verifique se não é nulo

SELECT blah FROM t WHERE blah NOT IN
        (SELECT someotherBlah FROM t2 WHERE someotherBlah IS NOT NULL )
Mihai
fonte
Eu tive um problema com a consulta de junção externa que não retornou nenhum registro em situações especiais. Por isso, verifiquei esta solução para o cenário de registros nulos e existentes e funcionou para mim. Se ocorrerem outros problemas, serei mencionado aqui, muito obrigado.
QMaster 23/02
1

isto é para o menino:

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

isso funciona independentemente das configurações da ansi

CB
fonte
para a pergunta original: B: selecione 'true' onde 3 não estão em (1, 2, nulo), deve-se fazer uma maneira de remover nulos, por exemplo, selecione 'true' onde 3 não está em (1, 2, isnull (null, 0) ) a lógica geral é que, se NULL é a causa, encontre uma maneira de remover valores NULL em alguma etapa da consulta.
selecionar party_code de abc como um onde party_code não em (party_code selecione a partir xyz onde party_code não é nulo) mas boa sorte se você esqueceu o campo permite valores nulos, o que é frequentemente o caso
1

SQL usa lógica de três valores para valores verdadeiros. A INconsulta produz o resultado esperado:

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

Mas adicionar a NOTnão inverte os resultados:

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

Isso ocorre porque a consulta acima é equivalente ao seguinte:

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

Aqui está como a cláusula where é avaliada:

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

Notar que:

  1. A comparação envolvendo NULL rendimentosUNKNOWN
  2. A ORexpressão em que nenhum dos operandos está TRUEe pelo menos um operando é UNKNOWNproduzida UNKNOWN( ref )
  3. A NOTde UNKNOWNrendimentos UNKNOWN( ref )

Você pode estender o exemplo acima para mais de dois valores (por exemplo, NULL, 1 e 2), mas o resultado será o mesmo: se um dos valores for NULL, nenhuma linha corresponderá.

Salman A
fonte