Como selecionar linhas sem entrada correspondente em outra tabela?


323

Estou fazendo algum trabalho de manutenção em um aplicativo de banco de dados e descobri que, alegria de alegria, mesmo que os valores de uma tabela estejam sendo usados ​​no estilo de chaves estrangeiras, não há restrições de chave estrangeira nas tabelas.

Estou tentando adicionar restrições FK nessas colunas, mas estou descobrindo isso, porque já existe uma carga inteira de dados inválidos nas tabelas de erros anteriores que foram ingenuamente corrigidos, preciso encontrar as linhas que não corresponda à outra tabela e exclua-as.

Encontrei alguns exemplos desse tipo de consulta na Web, mas todos parecem fornecer exemplos e não explicações, e não entendo por que eles funcionam.

Alguém pode me explicar como construir uma consulta que retorna todas as linhas sem partidas em outra tabela, e o que está fazendo, para que eu possa fazer essas consultas mim mesmo, em vez de vir correndo para SO para cada tabela nesta bagunça que tem sem restrições de FK?

Respostas:


614

Aqui está uma consulta simples:

SELECT t1.ID
FROM Table1 t1
    LEFT JOIN Table2 t2 ON t1.ID = t2.ID
WHERE t2.ID IS NULL

Os pontos principais são:

  1. LEFT JOINé usado; isso retornará TODAS as linhas de Table1, independentemente de haver ou não uma linha correspondente Table2.

  2. A WHERE t2.ID IS NULLcláusula; isso restringirá os resultados retornados apenas às linhas em que o ID retornado Table2é nulo - em outras palavras, NÃO há registro Table2para esse ID específico Table1. Table2.IDserá retornado como NULL para todos os registros de Table1onde o ID não corresponde Table2.


4
Falha se um ID for NULL #
Michael Michael

169
@ Michael - Se ter um NULLID válido no seu esquema, você pode ter problemas maiores, não concorda? :)
rinogo

1
isso funcionará mesmo que a tabela1 tenha mais registros do que a tabela2? se a tabela1 tiver 100 registros e a tabela2 tiver 200 registros (100 que correspondem / ingressam e 100 que não coincidem / ingressam), obteríamos todos os 200 registros retornados?
Juan Velez

1
Eu geralmente gosto de agrupar a junção esquerda como uma exibição de subconsulta / em linha para garantir que não haja interação entre a cláusula WHERE e a LEFT JOIN.
Andrew Wolfe

1
@Jas Ponto chave 1 da resposta, TODAS as linhas da primeira tabela, mesmo as que não correspondem à condição t1.ID = t2.ID da junção esquerda. Se você alterar a primeira linha para SELECT t1.ID, t2.IDe remover a linha WHERE, terá uma idéia melhor de como isso funciona.
Peter Laboš 19/08/19

97

Eu usaria a EXISTSexpressão, uma vez que é mais poderosa, ou seja, você pode escolher com mais precisão as linhas nas quais gostaria de ingressar, caso LEFT JOINprecise pegar tudo o que está na tabela unida. Sua eficiência é provavelmente a mesma do caso LEFT JOINcom teste nulo.

SELECT t1.ID
FROM Table1 t1
WHERE NOT EXISTS (SELECT t2.ID FROM Table2 t2 WHERE t1.ID = t2.ID)

Algo simples assim é facilmente manipulado pelo otimizador de consultas para melhor execução.
Andrew Wolfe

2
Sim, a principal vantagem EXISTSé a sua variabilidade.
Ondrej Bozek

1
Simples, elegante e resolveu o meu problema! Agradável!
MikeMighty

2
Na verdade, reduzi a velocidade de uma consulta que eu tinha de 7 segundos para 200ms ... (comparado a WHERE t2.id IS NULL) Obrigado.
Moti Korets

4
@MotiKorets quer dizer aumentou a velocidade :)
Ondrej Bozek

14
SELECT id FROM table1 WHERE foreign_key_id_column NOT IN (SELECT id FROM table2)

A Tabela 1 possui uma coluna à qual você deseja adicionar a restrição de chave estrangeira, mas foreign_key_id_columnnem todos os valores correspondem à idtabela 2.

  1. A seleção inicial lista os ids da tabela1. Essas serão as linhas que queremos excluir.
  2. A NOT INcláusula na instrução where limita a consulta apenas às linhas em que o valor no foreign_key_id_columnnão está na lista da tabela 2 ids.
  3. A SELECTdeclaração entre parênteses obterá uma lista de todos os ids que estão na tabela 2.

@ zb226: Seu link está relacionado aos limites da INcláusula com uma lista de valores literais. Não se aplica ao uso de uma INcláusula com o resultado de uma subconsulta. A resposta aceita para essa pergunta realmente resolve o problema usando uma subconsulta. (A grande lista de valores literais é problemática porque cria uma enorme expressão SQL A sub-consulta funciona bem porque, mesmo que a lista resultante é grande, a expressão SQL em si é pequena..)
Kannan Goundan

@KannanGoundan Você está absolutamente certo. Retirando o comentário defeituoso.
zb226 08/02/19

8

Onde T2está a tabela à qual você está adicionando a restrição:

SELECT *
FROM T2
WHERE constrained_field NOT
IN (
    SELECT DISTINCT t.constrained_field
    FROM T2 
    INNER JOIN T1 t
    USING ( constrained_field )
)

E exclua os resultados.


4

Vamos ter as 2 tabelas a seguir (salário e empregado) insira a descrição da imagem aqui

Agora eu quero esses registros da tabela de funcionários que não estão no salário. Podemos fazer isso de três maneiras:

  1. Usando junção interna
select * from employee
where id not in(select e.id from employee e inner join salary s on e.id=s.id)

insira a descrição da imagem aqui

  1. Usando junção externa esquerda
select * from employee e 
left outer join salary s on e.id=s.id  where s.id is null

insira a descrição da imagem aqui

  1. Usando junção completa
select * from employee e
full outer join salary s on e.id=s.id where e.id not in(select id from salary)

insira a descrição da imagem aqui


2

Da pergunta semelhante aqui MySQL Inner Junte-consulta para obter registros não presentes em outros Tabela eu tenho essa para o trabalho

SELECT * FROM bigtable 
LEFT JOIN smalltable ON bigtable.id = smalltable.id 
WHERE smalltable.id IS NULL

smalltableé onde você tem registros ausentes, bigtableé onde você tem todos os registros. A consulta lista todos os registros que não existem, smalltablemas existem no bigtable. Você pode substituir idpor qualquer outro critério correspondente.


0

Você pode optar por Visualizações, como mostrado abaixo:

CREATE VIEW AuthorizedUserProjectView AS select t1.username as username, t1.email as useremail, p.id as projectid, 
(select m.role from userproject m where m.projectid = p.id and m.userid = t1.id) as role 
FROM authorizeduser as t1, project as p

e trabalhe na visualização para selecionar ou atualizar:

select * from AuthorizedUserProjectView where projectid = 49

que produz o resultado como mostrado na figura abaixo, ou seja, para a coluna não correspondente, o nulo foi preenchido.

[Result of select on the view][1]

0

Não sei qual é otimizado (comparado a @AdaTheDev), mas este parece ser mais rápido quando eu uso (pelo menos para mim)

SELECT id  FROM  table_1 EXCEPT SELECT DISTINCT (table1_id) table1_id FROM table_2

Se você deseja obter outro atributo específico, pode usar:

SELECT COUNT(*) FROM table_1 where id in (SELECT id  FROM  table_1 EXCEPT SELECT DISTINCT (table1_id) table1_id FROM table_2);


-2

Você pode fazer algo assim

   SELECT IFNULL(`price`.`fPrice`,100) as fPrice,product.ProductId,ProductName 
          FROM `products` left join `price` ON 
          price.ProductId=product.ProductId AND (GeoFancingId=1 OR GeoFancingId 
          IS NULL) WHERE Status="Active" AND Delete="No"

-6

Como selecionar linhas sem entrada correspondente na tabela Both?

    selecione * em [dbo]. [EmppDetails] e
     junção direita [Empregado]. [Gênero] d em e.Gid = d.Gid
    onde e.Gid é nulo

    União 
    selecione * em [dbo]. [EmppDetails] e
     junção esquerda [Empregado]. [Gênero] d em e.Gid = d.Gid
    onde d.Gid é nulo

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.