SQL "selecionar onde não está na subconsulta" não retorna resultados

130

Isenção de responsabilidade: descobri o problema (acho), mas queria adicionar esse problema ao Stack Overflow, pois não conseguia (facilmente) encontrá-lo em qualquer lugar. Além disso, alguém pode ter uma resposta melhor do que eu.

Eu tenho um banco de dados onde uma tabela "Comum" é referenciada por várias outras tabelas. Eu queria ver quais registros na tabela Common ficaram órfãos (ou seja, não tinham referências de nenhuma das outras tabelas).

Eu executei esta consulta:

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

Eu sei que existem registros órfãos, mas nenhum registro foi retornado. Por que não?

(Este é o SQL Server, se for o caso.)

Jeremy Stein
fonte
Este stackoverflow.com/a/129152/1667619 responde muito bem à pergunta POR QUÊ.
Ruchan 12/07/19

Respostas:

234

Atualizar:

Estes artigos no meu blog descrevem as diferenças entre os métodos em mais detalhes:


Existem três maneiras de fazer essa consulta:

  • 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_idnão é anulável, todas essas consultas são semanticamente iguais.

Quando é nulo, NOT INé diferente, pois IN(e, portanto, NOT IN) retorna NULLquando um valor não corresponde a nada em uma lista que contém a NULL.

Isso pode ser confuso, mas pode se tornar mais óbvio se lembrarmos da sintaxe alternativa para isso:

common_id = ANY
(
SELECT  common_id
FROM    table1 t1
)

O resultado dessa condição é um produto booleano de todas as comparações na lista. Obviamente, um único NULLvalor produz o NULLresultado que renderiza o resultado inteiro NULLtambém.

Nunca podemos dizer definitivamente que isso common_idnão é igual a nada dessa lista, pois pelo menos um dos valores é NULL.

Suponha que tenhamos esses dados:

common

--
1
3

table1

--
NULL
1
2

LEFT JOIN / IS NULLe NOT EXISTSretornará 3, NOT INnão retornará nada (pois sempre será avaliado como um FALSEou NULL).

In MySQL, no caso de coluna não anulável, LEFT JOIN / IS NULLe NOT INé um pouco (vários por cento) mais eficiente que NOT EXISTS. Se a coluna for anulável, NOT EXISTSé a mais eficiente (novamente, não muito).

Em Oracle, todas as três consultas geram os mesmos planos (um ANTI JOIN).

Em SQL Server, NOT IN/ NOT EXISTSsão mais eficientes, pois LEFT JOIN / IS NULLnão podem ser otimizados para um ANTI JOINpor seu otimizador.

Em PostgreSQL, LEFT JOIN / IS NULLe NOT EXISTSsão mais eficientes do que NOT IN, seno, eles são otimizados para an Anti Join, while NOT INuses hashed subplan(ou até simples, subplanse a subconsulta for muito grande para o hash)

Quassnoi
fonte
8
Ótima resposta! Obrigado!
StevenMcD
isto é incrível e muito útil
Kavun
1
+1 porque, quatro anos e meio depois, essa resposta me ajudou com um problema que me deixou perplexo!
Carson63000
@ Carson63000 Snap! Eu pensei que eu estava ficando louco antes que eu vi esta resposta
Bobby
1
@IstiaqueAhmed: NOT EXISTSavalia como TRUE se a consulta dentro dela retornar alguma linha. SELECT NULLcomo poderia ser SELECT *ou SELECT 1ou qualquer outra coisa, o NOT EXISTSpredicado não olha para os valores das linhas, apenas os conta.
Quassnoi
36

Se você deseja que o mundo seja um local booleano de dois valores, você deve evitar o caso nulo (terceiro valor).

Não escreva cláusulas IN que permitam valores nulos no lado da lista. Filtre-os!

common_id not in
(
  select common_id from Table1
  where common_id is not null
)
Amy B
fonte
6
nulos na lista de cláusulas são um motivo comum para a falta de resultados da consulta.
217 Amy B
'Ao comparar com um nulo, a resposta é desconhecida' - da resposta de @Jeremy Stein. De common_id not in, ainda podemos ter um common_idvalor que é NULL. Portanto, o problema de não obter resultados ainda persiste?
Istiaque Ahmed 10/11
5

Tabela1 ou Tabela2 possui alguns valores nulos para common_id. Use esta consulta:

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)
Jeremy Stein
fonte
1
E se houver dados em uma tabela, mas não na outra? Você quer "e" ou "ou" lá?
Philip Kelley
1
Estou procurando registros não mencionados em nenhuma tabela, então quero AND. Vou esclarecer a questão.
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)
patmortech
fonte
4

Apenas fora do topo da minha cabeça...

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

Fiz alguns testes e aqui estavam meus resultados, a resposta de wrt @ patmortech e os comentários de @ rexem.

Se Tabela1 ou Tabela2 não estiver indexada no commonID, você fará uma varredura de tabela, mas a consulta do @ patmortech ainda será duas vezes mais rápida (para uma tabela mestre de 100K linhas).

Se nenhum deles estiver indexado no commonID, você realiza duas varreduras de tabela e a diferença é insignificante.

Se ambos estiverem indexados no commonID, a consulta "não existe" será executada em 1/3 do tempo.

Austin Salonen
fonte
1
Isso deve ser um AND na cláusula where. Caso contrário, isso funciona.
Jeremy Stein
1
alterado por seu comentário. O "ou" seleciona órfãos em qualquer tabela.
Austin Salonen
1
Isso é melhor. A propósito, existe alguma razão para eu usar junções externas em vez da subconsulta?
Jeremy Stein
3
A legibilidade é a principal. Eu suspeito que um plano de execução melhor seria gerado, mas sem um plano de consulta, não posso confirmar.
Austin Salonen
2
Essa abordagem é pior do que usar NOT EXISTS - a junção resulta na busca de mais linhas do que o necessário, e os resultados comparados para as colunas são nulos. E NOT EXISTS é mais legível para inicializar.
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
manji
fonte
1
Essa abordagem é pior do que usar NOT EXISTS - a junção resulta em buscar mais linhas do que o necessário, e os resultados comparados para as colunas são nulos. Funciona, mas o desempenho não será tão bom - possivelmente pior do que usar IN com subconsultas correlacionadas.
OMG Ponies
3

Vamos supor esses valores para common_id:

Common - 1
Table1 - 2
Table2 - 3, null

Queremos que a linha no Common retorne, porque não existe em nenhuma das outras tabelas. No entanto, o nulo joga em uma chave de macaco.

Com esses valores, a consulta é equivalente a:

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

Isso é equivalente a:

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

É aqui que o problema começa. Ao comparar com um nulo, a resposta é desconhecida . Portanto, a consulta se reduz a

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

falso ou desconhecido é desconhecido:

select *
from Common
where true
and not (unknown)

verdadeiro e não desconhecido também é desconhecido:

select *
from Common
where unknown

A condição where não retorna registros onde o resultado é desconhecido, portanto, não recuperamos registros.

Uma maneira de lidar com isso é usar o operador existente em vez de entrar. Existe nunca retorna desconhecido, porque opera em linhas e não em colunas. (Uma linha existe ou não; nada dessa ambiguidade nula no nível da linha!)

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)
Jeremy Stein
fonte
2

isso funcionou para mim :)

selecione * de Comum

Onde

common_id não está em (selecione ISNULL (common_id, 'dummy-data') na Tabela1)

e common_id não está em (selecione ISNULL (common_id, 'dummy-data') na Tabela2)

arqueado
fonte
@marlar, as subconsultas sempre retornam 1 ou 0, não uma lista de valores. Então, como será a NOT INperformance lá?
Istiaque Ahmed
0
select *,
(select COUNT(ID)  from ProductMaster where ProductMaster.CatID = CategoryMaster.ID) as coun 
from CategoryMaster
Donga jayesh
fonte
0

Eu tinha um exemplo em que estava olhando para cima e, como uma tabela continha o valor como um dobro, a outra como uma sequência, elas não coincidiam (ou não correspondiam sem uma conversão). Mas apenas NÃO IN . Como SELECT ... IN ... funcionou. Estranho, mas pensei em compartilhar caso mais alguém encontre essa solução simples.

resgates
fonte
0

Siga o exemplo abaixo para entender o tópico acima:

Além disso, você pode visitar o link a seguir para conhecer o 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
.......

Mas se usarmos NOT INnesse caso, não obteremos dados.

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

nenhum dado encontrado

Isso está acontecendo porque ( select department_id from hr.employees) está retornando um valor nulo e a consulta inteira é avaliada como falsa. Podemos ver isso se alterarmos o SQL ligeiramente como abaixo e manipularmos valores nulos com a função NVL.

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

Agora estamos obtendo dados:

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

Mais uma vez, estamos obtendo dados, pois lidamos com o valor nulo com a função NVL.

Rajesh Sarkar
fonte
Os resultados do SQl não estão aparecendo em forma de tabela, por favor, informe-me.
Rajesh Sarkar