SQL - найти записи из одной таблицы, которые не существуют в другой

310

У меня есть следующие две таблицы SQL (в 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 |
+----+------+--------------+

Как мне узнать, какие звонки были сделаны людьми, которых phone_numberнет в Phone_book? Желаемый результат будет:

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

Любая помощь приветствуется.

Филип Мортон
источник

Ответы:

439

Есть несколько способов сделать это с различной эффективностью, в зависимости от того, насколько хорош ваш оптимизатор запросов, и относительного размера ваших двух таблиц:

Это самое короткое утверждение, и оно может быть самым быстрым, если ваша телефонная книга очень короткая:

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

в качестве альтернативы (благодаря Alterlife )

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

или (благодаря WOPR)

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

(игнорируя это, как уже говорили другие, обычно лучше выбрать только те столбцы, которые вы хотите, а не ' *')

Альнитак
источник
1
избегайте IN, используйте EXISTS - подсказка есть в названии вопроса
annakata
28
Левое внешнее соединение, вероятно, является самым быстрым в общем случае, поскольку оно предотвращает повторное выполнение подзапроса.
WOPR
Не придирчиво, но подзапрос моего предложения возвращает <code> select 'x' </ code>, а не <code> select * </ code>
Alterlife
да - руководство MySQL предполагает, что это нормально для запроса «EXISTS»
Alnitak
2
@Alnitak: Во втором запросе вам не нужно SELECT *в подзапросе. Вместо этого, например SELECT 1, должно быть достаточно.
Александр Абакумов
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

Следует удалить подзапрос, позволяющий оптимизатору запросов работать со своей магией.

Также избегайте «SELECT *», потому что он может сломать ваш код, если кто-то изменит базовые таблицы или представления (и это неэффективно).

WOPR
источник
10
Как правило, это наиболее эффективный метод, поскольку он не выполняет несколько проходов на втором столе ... надеюсь, что некоторые люди читают кометы.
Nerdfest
3
Я предпочел бы надеяться, что люди профилируют: если вы не являетесь гуру высокой производительности SQL, заранее сказать, что будет самым быстрым, довольно сложно (и зависит от используемого вами механизма СУБД).
Борцмейер
2
Система обозначений Big O легко подскажет, что вы можете ожидать в этом случае быстрее всего. Это порядки величины разные.
Jonesopolis
Посмотрите ответ Afterlife и мой комментарий там, если 1:Nмежду вашими двумя таблицами есть связь. Или добавить , DISTINCTкак показано в ответ Владо по
ToolmakerSteve
25

Приведенный ниже код будет немного более эффективным, чем ответы, представленные выше, при работе с большими наборами данных.

SELECT * FROM Call WHERE 
NOT EXISTS (SELECT 'x' FROM Phone_book where 
Phone_book.phone_number = Call.phone_number)
Alterlife
источник
1
Как всегда, стоит профилировать производительность запросов по отношению к целевому набору данных, чтобы выбрать наиболее эффективный. В наши дни оптимизаторы SQL достаточно хороши, поэтому результаты производительности часто удивляют.
Грег Хьюгилл
1
Преимущество этого подхода (по сравнению с LEFT OUTER JOIN от WOPR) состоит в том, что он избегает возврата нескольких строк на строку Call, если в нем есть несколько совпадающих строк Phone_book. То есть, если есть 1:Nсвязь между вашими двумя таблицами.
ToolmakerSteve
Я бы начал с этого - он прямо представляет намерение. Если производительность недостаточно хорошая, убедитесь, что существуют соответствующие индексы. Только тогда попробуйте менее очевидное LEFT OUTER JOIN, посмотрите, лучше ли его производительность.
ToolmakerSteve
6
SELECT DISTINCT Call.id 
FROM Call 
LEFT OUTER JOIN Phone_book USING (id) 
WHERE Phone_book.id IS NULL

Это вернет дополнительные идентификаторы, которые отсутствуют в вашей таблице Phone_book.

Владо
источник
4

думаю

SELECT CALL.* FROM CALL LEFT JOIN Phone_book ON 
CALL.id = Phone_book.id WHERE Phone_book.name IS NULL
Nat Geo
источник
idСтолбец в callтаблице не то же самое значение, что и idстолбец в Phone_bookтаблице, так что вы не можете присоединиться на эти ценности. Смотрите ответ WOPR для аналогичного подхода.
Майкл Фредриксон
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
1
SELECT name, phone_number FROM Call a
WHERE a.phone_number NOT IN (SELECT b.phone_number FROM Phone_book b)
JoshYates1980
источник
Это не дает ответа на вопрос. Чтобы критиковать или запросить разъяснения у автора, оставьте комментарий под своим постом. - Из Обзора
Деннис Кричел
@DennisKriechel обновил запрос, чтобы он был более специфичным для вопроса.
JoshYates1980
1

С другой стороны,

select id from call
minus
select id from phone_number
elfekz
источник
1
Не уверен, что это отвечает на вопрос как есть (хотя оператор МИНУС) является новым дополнением. Это закончилось в очереди низкого качества - вы могли бы улучшить этот ответ.
ste-fu