SQL - encontre registros de uma tabela que não existem em outra

310

Eu tenho as seguintes duas tabelas SQL (no MySQL):

Phone_book
+----+------+--------------+
| id | name | phone_number |
+----+------+--------------+
| 1  | John | 111111111111 |
+----+------+--------------+
| 2  | Jane | 222222222222 |
+----+------+--------------+

Call
+----+------+--------------+
| id | date | phone_number |
+----+------+--------------+
| 1  | 0945 | 111111111111 |
+----+------+--------------+
| 2  | 0950 | 222222222222 |
+----+------+--------------+
| 3  | 1045 | 333333333333 |
+----+------+--------------+

Como descobrir quais chamadas foram feitas por pessoas que phone_numbernão estão na Phone_book? A saída desejada seria:

Call
+----+------+--------------+
| id | date | phone_number |
+----+------+--------------+
| 3  | 1045 | 333333333333 |
+----+------+--------------+

Qualquer ajuda seria muito apreciada.

Philip Morton
fonte

Respostas:

439

Há várias maneiras diferentes de fazer isso, com eficiência variável, dependendo da qualidade do otimizador de consultas e do tamanho relativo das duas tabelas:

Esta é a declaração mais curta e pode ser mais rápida se a sua agenda telefônica for muito curta:

SELECT  *
FROM    Call
WHERE   phone_number NOT IN (SELECT phone_number FROM Phone_book)

alternativamente (graças a Alterlife )

SELECT *
FROM   Call
WHERE  NOT EXISTS
  (SELECT *
   FROM   Phone_book
   WHERE  Phone_book.phone_number = Call.phone_number)

ou (graças ao WOPR)

SELECT * 
FROM   Call
LEFT OUTER JOIN Phone_Book
  ON (Call.phone_number = Phone_book.phone_number)
  WHERE Phone_book.phone_number IS NULL

(ignorando que, como já foi dito, normalmente é melhor selecionar apenas as colunas que você deseja, não ' *')

Alnitak
fonte
1
EM evitar, uso EXISTE - a dica é no título questão
annakata
28
A junção externa esquerda é provavelmente a mais rápida no caso geral, pois impede a execução repetida da subconsulta.
WOPR
Não é exigente, mas a subconsulta na minha sugestão retorna <code> selecione 'x' </code> e não <code> selecione * </code>
Alterlife
yes - O manual do MySQL sugere que isso é normal para uma consulta 'EXISTS'
Alnitak
2
@ Alnitak: Na segunda consulta, você não precisa SELECT *na subconsulta. Em vez disso, por exemplo SELECT 1, deve ser bastante bonito.
Alexander Abakumov
90
SELECT Call.ID, Call.date, Call.phone_number 
FROM Call 
LEFT OUTER JOIN Phone_Book 
  ON (Call.phone_number=Phone_book.phone_number) 
  WHERE Phone_book.phone_number IS NULL

Deve remover a subconsulta, permitindo que o otimizador de consultas trabalhe sua mágica.

Além disso, evite "SELECT *", pois isso pode danificar seu código se alguém alterar as tabelas ou visualizações subjacentes (e é ineficiente).

WOPR
fonte
10
Este é geralmente o método mais eficiente, pois não realiza várias passagens na segunda tabela ... espero que algumas pessoas estejam lendo os comunicados.
Nerdfest
3
Eu preferiria que as pessoas criassem perfil: a menos que você seja um dos principais gurus do desempenho do SQL, dizer com antecedência qual será o mais rápido é bastante difícil (e depende do mecanismo do DBMS usado).
Bortzmeyer
2
A notação Big O dirá facilmente o que você pode esperar ser o mais rápido nesse caso. São ordens de magnitude diferentes.
Jonesopolis 30/09/16
Veja a resposta do Afterlife e meu comentário, se houver um 1:Nrelacionamento entre suas duas tabelas. Ou adicione DISTINCTcomo visto na resposta de Vlado
ToolmakerSteve
25

O código abaixo seria um pouco mais eficiente do que as respostas apresentadas acima ao lidar com conjuntos de dados maiores.

SELECT * FROM Call WHERE 
NOT EXISTS (SELECT 'x' FROM Phone_book where 
Phone_book.phone_number = Call.phone_number)
Alterlife
fonte
1
Como sempre, vale a pena criar um perfil do desempenho das consultas no seu conjunto de dados de destino para escolher a que tem o melhor desempenho. Atualmente, os otimizadores de SQL são bons o suficiente para que os resultados de desempenho sejam surpreendentes.
Greg Hewgill
1
Uma vantagem dessa abordagem (vs. LEFT OUTER JOIN do WOPR) é que ela evita retornar várias linhas por linha Call, se houver várias linhas correspondentes Phone_book. Ou seja, se houver um 1:Nrelacionamento entre suas duas tabelas.
Home
Eu começaria com este - representa diretamente a intenção. Se o desempenho não for bom o suficiente, verifique se existem índices apropriados. Só então, tente o menos óbvio LEFT OUTER JOIN, veja se seu desempenho é melhor.
Home
6
SELECT DISTINCT Call.id 
FROM Call 
LEFT OUTER JOIN Phone_book USING (id) 
WHERE Phone_book.id IS NULL

Isso retornará os IDs extras ausentes na tabela Phone_book.

Vlado
fonte
4

eu acho que

SELECT CALL.* FROM CALL LEFT JOIN Phone_book ON 
CALL.id = Phone_book.id WHERE Phone_book.name IS NULL
National Geographic
fonte
A idcoluna na calltabela não tem o mesmo valor que a idcoluna na Phone_booktabela, portanto você não pode ingressar nesses valores. Veja a resposta do WOPR para uma abordagem semelhante.
Michael Fredrickson
3
SELECT t1.ColumnID,
CASE 
    WHEN NOT EXISTS( SELECT t2.FieldText  
                     FROM Table t2 
                     WHERE t2.ColumnID = t1.ColumnID) 
    THEN t1.FieldText
    ELSE t2.FieldText
END FieldText       
FROM Table1 t1, Table2 t2
Harvinder Sidhu
fonte
Isto irá voltar dados de uma tabela se os dados não está presente em outra mesa para a mesma coluna
Harvinder Sidhu
1
SELECT name, phone_number FROM Call a
WHERE a.phone_number NOT IN (SELECT b.phone_number FROM Phone_book b)
JoshYates1980
fonte
Isso não fornece uma resposta para a pergunta. Para criticar ou solicitar esclarecimentos a um autor, deixe um comentário abaixo da postagem. - Do comentário
Dennis Kriechel
A @DennisKriechel atualizou a consulta para que seja mais específica à pergunta.
precisa saber é o seguinte
1

Alternativamente,

select id from call
minus
select id from phone_number
elfekz
fonte
1
Não tenho certeza se isso responde à pergunta como está (embora o operador MENOS) seja uma nova adição. Isso acabou na fila de baixa qualidade - você pode aprimorar esta resposta.
21317 st-fu