Pergunta

Esta questão surgiu quando cheguei diferentes contagens de registros para o que eu achava que eram consultas idênticas um usando uma restrição not in where eo outro um left join. A tabela na restrição not in tinha um valor nulo (dados ruins) que causou essa consulta para devolver uma contagem de 0 registros. Eu meio que entendo por que, mas eu poderia usar alguma ajuda agarrar completamente o conceito.

Para indicá-lo simplesmente, por que consulta um retorno 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)

Este foi no SQL Server 2005. Eu também achei que chamar set ansi_nulls off provoca B para retornar um resultado.

Foi útil?

Solução

Consulta A é o mesmo que:

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

Desde 3 = 3 é verdade, você obter um resultado.

Consulta B é o mesmo que:

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

Quando ansi_nulls está ligado, 3 <> null é desconhecido, assim que os avalia predicados para DESCONHECIDO, e você não receber nenhuma linha.

Quando ansi_nulls está desligado, 3 <> null é verdade, então os avalia de predicados como verdadeiro, e você terá uma linha.

Outras dicas

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

Seus primeiros consulta retorna resultados como o Where avalia cláusula 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 FALSE você pode facilmente testá-lo chamando:

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

Ambas as consultas lhe dará nenhum resultado

Se o desconhecido foi o mesmo que FALSE, em seguida, assumindo que a primeira consulta lhe daria FALSE o segundo teria que avaliar como TRUE como teria sido o mesmo que não (false).
Isso não é o caso.

Há um muito bom sobre este assunto em SqlServerCentral .

Toda a questão dos nulos e de três valores Logic pode ser um pouco confuso no início, mas é essencial para compreender a fim de escrever consultas corretas em TSQL

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

NOT IN retorna 0 registros quando comparado com um valor desconhecido

Desde NULL é um desconhecido, uma consulta NOT IN contendo uma NULL ou NULLs na lista de possíveis valores sempre retornará registros 0 já que não há maneira de ter certeza de que o valor NULL não é o valor que está sendo testado.

Comparar como nulo é indefinido, a menos que você usa é nulo.

Assim, comparando-3 para NULL (query A), ele retorna indefinido.

i. Select 'verdadeira', onde 3 em (1,2, null) e SELECIONAR 'true', onde 3 não em (1,2, null)

irá produzir o mesmo resultado, como NOT (indefinido) ainda não está definida, mas não é verdade

O título desta questão no momento da escrita é

valores de restrição e NULL SQL NÃO EM

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

No entanto, especialmente tendo em conta o texto do título, quero salientar que algumas declarações feitas aqui são potencialmente enganosa declarações, aquelas ao longo das linhas de (parafraseando)

Quando os avalia predicados para DESCONHECIDO você não tem nenhuma linha.

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

Considere esta tabela muito simples com duas restrições tiradas diretamente dos predicados na questão (e abordada em uma excelente resposta por @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 @ resposta de Brannon, a primeira restrição (usando IN) avalia a verdade e a segunda restrição (usando NOT IN) avalia para DESCONHECIDO. No entanto , a inserção bem-sucedida! Portanto, neste caso, não é estritamente correto dizer, "você não tem nenhuma linha" porque temos de fato tem uma linha inserida como resultado.

O efeito acima é o correto no que respeita ao SQL-92 padrão. Comparar e contrastar a seguinte seção da especificação SQL-92

7,6 onde cláusula

O resultado do é uma tabela das linhas de T para que o resultado da condição de pesquisa é verdade.

4.10 As restrições de integridade

A verificação de restrição de tabela é satisfeita se e somente se o especificado procurar condição não é falso para qualquer linha de uma tabela.

Em outras palavras:

No SQL DML, as linhas são removidas do resultado quando os avalia WHERE para DESCONHECIDO porque não satisfazer a condição "é verdade".

No SQL DDL (isto é, restrições), as linhas não são removidos a partir do resultado quando avaliam a DESCONHECIDO porque não satisfazer a condição "não é falsa".

Embora os efeitos em SQL DML e DDL SQL respectivamente pode parecer contraditório, não há razão prática para dar resultados DESCONHECIDO o 'benefício da dúvida', permitindo-lhes para satisfazer uma restrição (mais corretamente, permitindo-lhes para não falhar para satisfazer uma restrição): sem este comportamento, cada constrangimentos teria que lidar explicitamente nulos e que seria muito insatisfatória a partir de uma perspectiva de design de idioma (para não mencionar, uma dor direita para codificadores)

!

P.S. se você está encontrando-o como um desafio para seguir essa lógica como "desconhecido não deixa de satisfazer uma restrição" como estou a escrevê-lo, então considerar você pode dispensar tudo isso simplesmente evitando colunas anuláveis ??em SQL DDL e nada em SQL DML que produz nulos (por exemplo, junções externas)!

A, 3 é testado quanto à igualdade contra cada um dos membros do conjunto, obtendo-se (FALSE, FALSO, VERDADEIRO, DESCONHECIDO). Dado que um dos elementos é TRUE, a condição é verdadeira. (Também é possível que algum curto-circuito ocorre aqui, para que ele realmente pára assim que ele atinge o primeiro verdadeiro e nunca avalia 3 = NULL.)

Em B, eu acho que está avaliando a condição como NOT (3 em (1,2, null)). Teste 3 para a igualdade contra os rendimentos de ajuste (FALSE, FALSE, desconhecido), que são agregados ao DESCONHECIDO. NOT (desconhecido) rendimentos DESCONHECIDO. Portanto, em geral a verdade da doença é desconhecida, que no final é essencialmente tratada como FALSE.

significa nulos e ausência de dados, ou seja, é desconhecido, e não um valor de dados de nada. É muito fácil para as pessoas de um fundo de programação para confundir isto porque em linguagens tipo C quando se utiliza ponteiros nulo é na verdade nada.

Assim, no primeiro caso 3 é, de facto, o conjunto de (1,2,3, null) tão verdadeiro é retornado

No segundo, contudo, você pode reduzi-la a

selecione 'true', onde 3 não em (null)

Portanto, nada é devolvido porque o analisador não sabe nada sobre o conjunto ao qual você está comparando-a - não é um conjunto vazio, mas um conjunto desconhecido. Usando (1, 2, null) não ajuda porque a (1,2) conjunto é obviamente falsa, mas então você está and'ing que contra o desconhecido, que é desconhecido.

Pode-se concluir a partir de respostas aqui que NOT IN (subquery) não lidar com valores nulos corretamente e deve ser evitado em favor de NOT EXISTS. No entanto, esta conclusão pode ser um prematuro. No cenário a seguir, creditada a Chris Data (Programação Banco de Dados e Projeto, Vol 2 Não 9, setembro de 1989), é NOT IN que lida com valores nulos corretamente e retorna o resultado correto, em vez de NOT EXISTS.

Considere um sp mesa para representar fornecedores (sno), que são conhecidos para fornecimento de peças (pno) em quantidade (qty). A tabela detém atualmente os seguintes valores:

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

Note que a quantidade é anulável ou seja, para ser capaz de gravar o fato de um fornecedor é conhecido por fornecimento de peças, mesmo que não se sabe em que quantidade.

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

As seguintes utilizações NOT IN para identificar corretamente fornecedor 'S2' apenas:

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 seguir consulta utiliza a mesma estrutura geral, mas com NOT EXISTS mas incorretamente inclui fornecedor 'S1' no resultado (isto é, para os quais 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
                      );

Assim NOT EXISTS não é a bala de prata que pode ter aparecido!

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

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

  • fornecedores sp conhecidos fornecimento de peças
  • fornecedores spq conhecidos fornecimento de peças em quantidades conhecidas

notando há provavelmente deve ser uma restrição de chave estrangeira onde as referências spq sp.

O resultado pode ser obtido usando o 'menos' operador relacional (sendo a palavra-chave EXCEPT no padrão SQL) 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;

Se você quiser filtro com NOT IN para uma subconsulta contendo nulos apenas verificar se não nulo

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

este é 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 do ansi configurações

Também isso pode ser de uso para saber a diferença lógica entre juntar-se, existe e em http://weblogs.sqlteam.com/mladenp/archive/ 2007/05/18 / 60210.aspx

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top