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
หลีกเลี่ยงการใช้ EXISTS - คำใบ้อยู่ในชื่อคำถาม
annakata

28
การรวมภายนอกด้านซ้ายอาจเร็วที่สุดในกรณีทั่วไปเนื่องจากป้องกันการเรียกใช้งานการสืบค้นย่อยซ้ำ
WOPR

ไม่ต้องจู้จี้จุกจิก แต่ข้อความค้นหาย่อยที่ฉันแนะนำจะส่งกลับ <code> เลือก 'x' </code> และไม่ใช่ <code> เลือก * </code>
Alterlife

ใช่ - คู่มือ MySQL แสดงให้เห็นว่านี่เป็นเรื่องปกติสำหรับข้อความค้นหา 'EXISTS'
Alnitak

2
@Alnitak: ในแบบสอบถามที่สองคุณไม่จำเป็นต้องSELECT *ใช้แบบสอบถามย่อย ตัวอย่างเช่นSELECT 1ควรจะสวยพอสมควร
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

ควรลบข้อความค้นหาย่อยเพื่อให้เครื่องมือเพิ่มประสิทธิภาพการสืบค้นสามารถใช้เวทย์มนตร์ได้

นอกจากนี้ให้หลีกเลี่ยง "SELECT *" เพราะรหัสของคุณอาจผิดพลาดหากมีคนเปลี่ยนแปลงตารางหรือมุมมองพื้นฐาน (และไม่มีประสิทธิภาพ)


10
นี่เป็นวิธีที่มีประสิทธิภาพมากที่สุดโดยที่มันไม่ได้ทำหลาย ๆ รอบในตารางที่สอง ... หวังว่าบางคนกำลังอ่าน comemnts
Nerdfest

3
ฉันอยากจะหวังว่าโปรไฟล์ผู้คน: ถ้าคุณไม่ใช่กูรูด้านประสิทธิภาพของ SQL การบอกล่วงหน้าว่าอะไรจะเร็วที่สุดนั้นค่อนข้างยาก (และขึ้นอยู่กับเอ็นจิน DBMS ที่คุณใช้)
bortzmeyer

2
สัญลักษณ์ Big O จะบอกคุณได้อย่างง่ายดายว่าคุณคาดหวังว่าจะเร็วที่สุดในกรณีนี้ มันมีขนาดต่างกัน
Jonesopolis

ดูคำตอบของ Afterlifeและความคิดเห็นของฉันที่นั่นหากมี1:Nความสัมพันธ์ระหว่างสองตารางของคุณ หรือเพิ่มDISTINCTดังที่เห็นในคำตอบของ Vlado
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

นี่จะส่งคืนรหัสพิเศษที่ขาดหายไปในตาราง Phone_book ของคุณ


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 สำหรับวิธีการที่คล้ายกัน
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

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

สิ่งนี้ไม่ได้ให้คำตอบสำหรับคำถาม หากต้องการวิจารณ์หรือขอคำชี้แจงจากผู้แต่งโปรดแสดงความคิดเห็นใต้โพสต์ของพวกเขา - จากการรีวิว
Dennis Kriechel

@DennisKriechel ข้อความค้นหาที่อัปเดตแล้วเพื่อให้เฉพาะเจาะจงกับคำถามมากขึ้น
JoshYates1980

1

อีกวิธีหนึ่งคือ

select id from call
minus
select id from phone_number

1
ไม่แน่ใจว่าสิ่งนี้ตอบคำถามตามที่เป็น (แม้ว่าลบ) เป็นตัวเพิ่มใหม่ จบลงด้วยคิวคุณภาพต่ำ - คุณอาจต้องการปรับปรุงคำตอบนี้
ste-fu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.