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 |
+----+------+--------------+

Будь-яка допомога буде дуже вдячна.

Відповіді:


438

Існує кілька різних способів зробити це з різною ефективністю, залежно від того, наскільки хороший ваш оптимізатор запитів та відносний розмір ваших двох таблиць:

Це найкоротший вислів, і він може бути найшвидшим, якщо телефонна книга дуже коротка:

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 *", оскільки він може порушити ваш код, якщо хтось змінить основні таблиці або представлення даних (і це неефективно).


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)

1
Як завжди, варто детально вивчити ефективність запитів щодо цільового набору даних, щоб вибрати той, який має найкращу ефективність. Сьогодні оптимізатори SQL досить хороші, що результати роботи часто дивують.
Грег Хьюгілл

1
Перевагою такого підходу (порівняно з ЛІВНІШНІЙ СПІЛЬНИК від 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

Це поверне зайві ідентифікатори, які відсутні в таблиці телефонної книги.


4

я думаю

SELECT CALL.* FROM CALL LEFT JOIN Phone_book ON 
CALL.id = Phone_book.id WHERE Phone_book.name IS NULL

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)

Це не дає відповіді на запитання. Щоб критикувати або вимагати роз'яснення у автора, залиште коментар під їх дописом. - З огляду
Денніс Крічель

@DennisKriechel оновив запит, щоб він був більш конкретним у питанні.
JoshYates1980

1

Крім того,

select id from call
minus
select id from phone_number

1
Не впевнений, що це відповідає на питання, як це (хоча оператор MINUS) є новим доповненням. Це опинилося в черзі низької якості - можливо, ви хочете покращити цю відповідь.
сте-фу
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.