SQL - tìm bản ghi từ một bảng không tồn tại trong bảng khác


310

Tôi đã có hai bảng SQL sau (trong 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 |
+----+------+--------------+

Làm cách nào để biết được cuộc gọi nào được thực hiện bởi những người phone_numberkhông tham gia Phone_book? Đầu ra mong muốn sẽ là:

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

Bất kì sự trợ giúp nào đều được đánh giá cao.

Câu trả lời:


439

Có một số cách khác nhau để thực hiện việc này, với hiệu quả khác nhau, tùy thuộc vào mức độ tối ưu hóa truy vấn của bạn và kích thước tương đối của hai bảng của bạn:

Đây là tuyên bố ngắn nhất và có thể nhanh nhất nếu danh bạ điện thoại của bạn rất ngắn:

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

cách khác (nhờ Alterlife )

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

hoặc (nhờ WOPR)

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

(bỏ qua điều đó, như những người khác đã nói, thông thường tốt nhất là chỉ chọn các cột bạn muốn, không phải ' *')


1
tránh IN, sử dụng EXISTS - gợi ý có trong tiêu đề câu hỏi
annakata

28
Phép nối ngoài bên trái có lẽ là nhanh nhất trong trường hợp chung vì nó ngăn chặn việc thực hiện lặp lại truy vấn con.
WOPR

Không kén chọn, nhưng truy vấn con trong đề xuất của tôi trả về <code> chọn 'x' </ code> chứ không phải <code> select * </ code>
Alterlife

có - Hướng dẫn sử dụng MySQL cho thấy điều này là bình thường đối với truy vấn 'EXISTS'
Alnitak

2
@Alnitak: Trong truy vấn thứ hai bạn không cần SELECT *trong truy vấn con. Thay vào đó, ví dụ SELECT 1, nên là đủ đẹp.
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

Nên loại bỏ truy vấn con, cho phép trình tối ưu hóa truy vấn thực hiện phép thuật của nó.

Ngoài ra, tránh "CHỌN *" vì nó có thể phá vỡ mã của bạn nếu ai đó thay đổi các bảng hoặc dạng xem bên dưới (và nó không hiệu quả).


10
Đây thường là phương pháp hiệu quả nhất vì nó không thực hiện nhiều lượt trên bảng thứ hai ... hy vọng một số người đang đọc các comemnts.
Nerdfest

3
Tôi thà hy vọng rằng hồ sơ của mọi người: trừ khi bạn là một chuyên gia hiệu suất SQL hàng đầu, việc nói trước những gì sẽ nhanh nhất là khá khó khăn (và phụ thuộc vào công cụ DBMS bạn sử dụng).
bortzmeyer

2
Ký hiệu Big O sẽ dễ dàng cho bạn biết những gì bạn có thể mong đợi là nhanh nhất trong trường hợp này. Đó là những mệnh lệnh có độ lớn khác nhau.
Jonesopolis

Xem câu trả lời của Afterlife và nhận xét của tôi ở đó, nếu có 1:Nmối quan hệ giữa hai bảng của bạn. HOẶC thêm DISTINCTnhư đã thấy trong câu trả lời của Vlado
ToolmakerSteve

25

Mã dưới đây sẽ hiệu quả hơn một chút so với các câu trả lời được trình bày ở trên khi xử lý các bộ dữ liệu lớn hơn.

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

1
Như mọi khi, đáng để cấu hình hiệu năng của các truy vấn đối với tập dữ liệu đích của bạn để chọn một truy vấn có hiệu suất tốt nhất. Tối ưu hóa SQL là đủ tốt những ngày này mà kết quả hiệu suất thường đáng ngạc nhiên.
Greg Hewgill

1
Một lợi thế của phương pháp này (so với LEFT OUTER THAM GIA bởi WOPR) là nó tránh trả lại nhiều hàng trên mỗi hàng Call, nếu có nhiều hàng khớp nhau Phone_book. Đó là, nếu có một 1:Nmối quan hệ giữa hai bảng của bạn.
ToolmakerSteve

Tôi sẽ BẮT ĐẦU với cái này - nó trực tiếp đại diện cho ý định. Nếu hiệu suất không đủ tốt, đảm bảo các chỉ số thích hợp tồn tại. Chỉ sau đó, hãy thử ít rõ ràng hơn LEFT OUTER JOIN, xem hiệu suất của nó là tốt hơn.
ToolmakerSteve

6
SELECT DISTINCT Call.id 
FROM Call 
LEFT OUTER JOIN Phone_book USING (id) 
WHERE Phone_book.id IS NULL

Điều này sẽ trả về các id bổ sung bị thiếu trong bảng Phone_book của bạn.


4

tôi nghĩ

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

Các idcột trong callbảng không phải là giá trị tương tự như các idcột trong Phone_bookbảng, vì vậy bạn không thể tham gia vào những giá trị này. Xem câu trả lời của WOPR cho cách tiếp cận tương tự.
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

Điều này sẽ trả về dữ liệu của bạn từ một bảng nếu dữ liệu không có trong một bảng khác cho cùng một cột
Crawinder Sidhu

1
SELECT name, phone_number FROM Call a
WHERE a.phone_number NOT IN (SELECT b.phone_number FROM Phone_book b)

Điều này không cung cấp một câu trả lời cho câu hỏi. Để phê bình hoặc yêu cầu làm rõ từ một tác giả, hãy để lại nhận xét bên dưới bài đăng của họ. - Từ đánh giá
Dennis Kriechel 9/12/2015

@DennisKriechel cập nhật truy vấn để nó cụ thể hơn cho câu hỏi.
JoshYates1980

1

Ngoài ra,

select id from call
minus
select id from phone_number

1
Không chắc chắn điều này trả lời câu hỏi như (mặc dù toán tử MINUS) là một bổ sung mới. Điều này đã kết thúc trong hàng đợi chất lượng thấp - bạn có thể muốn nâng cao câu trả lời này.
ste-fu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.