SQL - menemukan catatan dari satu tabel yang tidak ada di tabel lain

310

Saya punya dua tabel SQL berikut (di 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 |
+----+------+--------------+

Bagaimana cara mengetahui panggilan mana yang dilakukan oleh orang-orang yang phone_numbertidak masuk Phone_book? Output yang diinginkan adalah:

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

Bantuan apa pun akan sangat dihargai.

Philip Morton
sumber

Jawaban:

439

Ada beberapa cara untuk melakukan ini, dengan efisiensi yang berbeda-beda, tergantung pada seberapa bagus pengoptimal kueri Anda, dan ukuran relatif dari dua tabel Anda:

Ini adalah pernyataan terpendek, dan mungkin tercepat jika buku telepon Anda sangat pendek:

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

sebagai alternatif (terima kasih kepada Alterlife )

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

atau (terima kasih kepada WOPR)

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

(Mengabaikan itu, seperti yang dikatakan orang lain, biasanya yang terbaik adalah memilih kolom yang Anda inginkan, bukan ' *')

Alnitak
sumber
1
Hindari IN, gunakan EXISTS - petunjuknya ada di judul pertanyaan
annakata
28
Sambungan luar kiri mungkin paling cepat dalam kasus umum karena mencegah eksekusi berulang dari subquery.
WOPR
Bukan untuk pilih-pilih, tetapi subquery pada saran saya mengembalikan <code> pilih 'x' </code> dan bukan <code> pilih * </code>
Alterlife
ya - manual MySQL menunjukkan bahwa ini adalah normal untuk kueri 'EXISTS'
Alnitak
2
@Alnitak: Di kueri kedua Anda tidak perlu SELECT *di subquery. Sebaliknya, misalnya SELECT 1, harus cukup cantik.
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

Harus menghapus subquery, memungkinkan pengoptimal kueri untuk melakukan keajaibannya.

Selain itu, hindari "SELECT *" karena dapat merusak kode Anda jika seseorang mengubah tabel atau tampilan yang mendasarinya (dan itu tidak efisien).

WOPR
sumber
10
Ini umumnya merupakan metode yang paling efisien karena tidak melakukan beberapa lintasan pada tabel kedua ... harap beberapa orang membaca comemnts.
Nerdfest
3
Saya lebih suka berharap bahwa profil orang: kecuali Anda seorang guru kinerja SQL top, mengatakan sebelumnya apa yang akan menjadi tercepat adalah cukup sulit (dan tergantung pada mesin DBMS yang Anda gunakan).
bortzmeyer
2
Notasi O besar akan dengan mudah memberi tahu Anda apa yang dapat Anda harapkan untuk menjadi tercepat dalam hal ini. Ini urutan besarnya berbeda.
Jonesopolis
Lihat jawaban Afterlife dan komentar saya di sana, jika ada 1:Nhubungan antara dua tabel Anda. ATAU tambahkan DISTINCTseperti terlihat dalam jawaban Vlado
ToolmakerSteve
25

Kode di bawah ini akan sedikit lebih efisien daripada jawaban yang disajikan di atas ketika berhadapan dengan kumpulan data yang lebih besar.

SELECT * FROM Call WHERE 
NOT EXISTS (SELECT 'x' FROM Phone_book where 
Phone_book.phone_number = Call.phone_number)
Alterlife
sumber
1
Seperti biasa, ada baiknya memprofilkan kinerja kueri terhadap dataset target Anda untuk memilih yang dengan kinerja terbaik. Pengoptimal SQL cukup baik hari ini bahwa hasil kinerja sering mengejutkan.
Greg Hewgill
1
Keuntungan dari pendekatan ini (vs. LEFT OUTER JOIN oleh WOPR) adalah bahwa ia menghindari mengembalikan beberapa baris per baris Call, jika ada beberapa baris yang cocok Phone_book. Artinya, jika ada 1:Nhubungan antara dua tabel Anda.
ToolmakerSteve
Saya akan MULAI dengan yang ini - itu secara langsung mewakili niat. Jika kinerja tidak cukup baik, pastikan ada indeks yang sesuai. Hanya kemudian, coba yang kurang jelas LEFT OUTER JOIN, lihat apakah kinerjanya lebih baik.
ToolmakerSteve
6
SELECT DISTINCT Call.id 
FROM Call 
LEFT OUTER JOIN Phone_book USING (id) 
WHERE Phone_book.id IS NULL

Ini akan mengembalikan id tambahan yang hilang di tabel Phone_book Anda.

Vlado
sumber
4

kupikir

SELECT CALL.* FROM CALL LEFT JOIN Phone_book ON 
CALL.id = Phone_book.id WHERE Phone_book.name IS NULL
Nat Geo
sumber
The idkolom dalam calltabel tidak nilai yang sama sebagai idkolom dalam Phone_booktabel, sehingga Anda tidak dapat bergabung pada nilai-nilai ini. Lihat jawaban WOPR untuk pendekatan serupa.
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
sumber
Ini akan mengembalikan data Anda dari satu tabel jika data tidak ada di tabel lain untuk kolom yang sama
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
sumber
Ini tidak memberikan jawaban untuk pertanyaan itu. Untuk mengkritik atau meminta klarifikasi dari penulis, tinggalkan komentar di bawah posting mereka. - Dari Ulasan
Dennis Kriechel
@DennisKriechel memperbarui kueri sehingga lebih spesifik untuk pertanyaan.
JoshYates1980
1

Kalau tidak,

select id from call
minus
select id from phone_number
elfekz
sumber
1
Tidak yakin ini menjawab pertanyaan sebagaimana adanya (meskipun MINUS) adalah tambahan baru. Ini berakhir dalam antrian berkualitas rendah - Anda mungkin ingin meningkatkan jawaban ini.
ste-fu