คำสั่ง SQL EXISTS ทำงานอย่างไร


89

ฉันพยายามเรียนรู้ SQL และมีปัญหาในการทำความเข้าใจคำสั่ง EXISTS ฉันเจอคำพูดนี้เกี่ยวกับ "มีอยู่จริง" และไม่เข้าใจบางอย่าง:

การใช้โอเปอเรเตอร์ที่มีอยู่แบบสอบถามย่อยของคุณสามารถส่งคืนค่าศูนย์หนึ่งแถวหรือหลายแถวและเงื่อนไขจะตรวจสอบว่าแบบสอบถามย่อยส่งคืนแถวใด ๆ หรือไม่ หากคุณดูที่ส่วนคำสั่ง select ของเคียวรีย่อยคุณจะเห็นว่าประกอบด้วยลิเทอรัลเดียว (1); เนื่องจากเงื่อนไขในแบบสอบถามที่มีเพียงต้องการทราบว่ามีการส่งคืนแถวจำนวนเท่าใดข้อมูลจริงที่แบบสอบถามย่อยที่ส่งกลับจึงไม่เกี่ยวข้อง

สิ่งที่ฉันไม่เข้าใจคือแบบสอบถามภายนอกรู้ได้อย่างไรว่าแบบสอบถามย่อยกำลังตรวจสอบแถวใด ตัวอย่างเช่น:

SELECT *
  FROM suppliers
 WHERE EXISTS (select *
                 from orders
                where suppliers.supplier_id = orders.supplier_id);

ฉันเข้าใจว่าหากรหัสจากซัพพลายเออร์และตารางคำสั่งซื้อตรงกันคำค้นหาย่อยจะส่งคืนจริงและคอลัมน์ทั้งหมดจากแถวที่ตรงกันในตารางของซัพพลายเออร์จะถูกส่งออก สิ่งที่ฉันไม่ได้รับคือวิธีที่แบบสอบถามย่อยสื่อสารว่าควรพิมพ์แถวใด (เช่นแถวที่มีรหัสซัพพลายเออร์ 25) หากส่งคืนเฉพาะจริงหรือเท็จ

สำหรับฉันแล้วดูเหมือนว่าไม่มีความสัมพันธ์ระหว่างแบบสอบถามภายนอกและแบบสอบถามย่อย

คำตอบ:


100

คิดแบบนี้:

สำหรับ 'แต่ละแถวจากSuppliersการตรวจสอบหากมี 'อยู่' แถวในที่Orderตารางที่มีคุณสมบัติตรงตามเงื่อนไขSuppliers.supplier_id(นี้มาจากด้านนอกค้นหา 'แถว' = Orders.supplier_idปัจจุบัน) เมื่อคุณพบแถวแรกที่ตรงกันให้หยุดตรงนั้น - WHERE EXISTSพอใจแล้ว

การเชื่อมโยงมหัศจรรย์ระหว่างแบบสอบถามภายนอกและแบบสอบถามย่อยอยู่ในความจริงที่Supplier_idส่งผ่านจากแบบสอบถามภายนอกไปยังแบบสอบถามย่อยสำหรับแต่ละแถวที่ประเมิน

หรือกล่าวอีกนัยหนึ่งคิวรีย่อยจะถูกเรียกใช้สำหรับแต่ละแถวในตารางของคิวรีภายนอก

ไม่เหมือนกับเคียวรีย่อยถูกดำเนินการทั้งหมดและได้รับ 'true / false' จากนั้นพยายามจับคู่เงื่อนไข 'จริง / เท็จ' กับเคียวรีภายนอก


7
ขอบคุณ! "ไม่เหมือนกับการสืบค้นย่อยที่ดำเนินการทั้งหมดและได้รับ 'จริง / เท็จ' จากนั้นพยายามจับคู่เงื่อนไข 'จริง / เท็จ' นี้ด้วยการสืบค้นนอก" คือสิ่งที่ชัดเจนสำหรับฉันฉันคิดอยู่เสมอว่านั่นเป็นวิธีการทำงานของการสืบค้นย่อย (และหลายครั้งที่พวกเขาทำ) แต่สิ่งที่คุณพูดนั้นสมเหตุสมผลเพราะการสืบค้นย่อยอาศัยการสืบค้นภายนอกดังนั้นจึงต้องดำเนินการหนึ่งครั้งต่อแถว
คลาเรนซ์หลิว

32

สำหรับฉันแล้วดูเหมือนว่าไม่มีความสัมพันธ์ระหว่างแบบสอบถามภายนอกและแบบสอบถามย่อย

คุณคิดว่าคำสั่ง WHERE ในตัวอย่าง EXISTS กำลังทำอะไรอยู่? คุณจะได้ข้อสรุปอย่างไรเมื่อการอ้างอิงผู้จัดหาไม่ได้อยู่ในส่วนคำสั่ง FROM หรือ JOIN ภายในประโยคคำสั่ง EXISTS

EXISTS ประเมินค่าสำหรับ / เท็จจริงและออกเป็น TRUE ในการแข่งขันครั้งแรกของเกณฑ์ - INนี่คือเหตุผลที่มันอาจจะเร็วกว่า โปรดทราบด้วยว่าคำสั่ง SELECT ใน EXISTS ถูกละเว้น - IE:

SELECT s.*
  FROM SUPPLIERS s
 WHERE EXISTS (SELECT 1/0
                 FROM ORDERS o
                WHERE o.supplier_id = s.supplier_id)

... ควรตีหารด้วยข้อผิดพลาดเป็นศูนย์ แต่จะไม่ WHERE clause เป็นส่วนที่สำคัญที่สุดของ EXISTS clause

นอกจากนี้โปรดทราบว่าการเข้าร่วมไม่ใช่การแทนที่โดยตรงสำหรับ EXISTS เนื่องจากจะมีเรกคอร์ดหลักที่ซ้ำกันหากมีระเบียนย่อยมากกว่าหนึ่งรายการที่เชื่อมโยงกับผู้ปกครอง


1
ฉันยังขาดอะไรบางอย่าง หากออกจากการแข่งขันครั้งแรกผลลัพธ์จะลงเอยอย่างไรโดยที่ o.supplierid = s.supplierid? มันจะไม่แสดงผลลัพธ์แรกแทนหรือ?
แดน

3
@ แดน: การEXISTSออกส่งคืน TRUE ในนัดแรก - เนื่องจากซัพพลายเออร์มีอยู่อย่างน้อยหนึ่งครั้งในตาราง ORDERS หากคุณต้องการดูข้อมูล SUPPLIER ซ้ำซ้อนเนื่องจากมีความสัมพันธ์ลูกมากกว่าหนึ่งรายการใน ORDERS คุณจะต้องใช้ JOIN แต่ส่วนใหญ่ไม่ต้องการให้มีการทำซ้ำและการเรียกใช้ GROUP BY / DISTINCT มีแนวโน้มที่จะเพิ่มค่าโสหุ้ยในแบบสอบถาม EXISTSมีประสิทธิภาพมากกว่าSELECT DISTINCT ... FROM SUPPLIERS JOIN ORDERS ...บน SQL Server ไม่ได้ทดสอบบน Oracle หรือ MySQL เมื่อเร็ว ๆ นี้
OMG Ponies

ฉันมีคำถามคือการจับคู่เสร็จสิ้นสำหรับทุกระเบียนที่เลือกในแบบสอบถามภายนอก เช่นเดียวกับเราดึงข้อมูลจากคำสั่งซื้อ 5 ครั้งหากมี 5 แถวที่เลือกจากซัพพลายเออร์
Rahul Kadukar

24

คุณสามารถให้ผลลัพธ์ที่เหมือนกันโดยใช้JOIN, EXISTS, INหรือINTERSECT:

SELECT s.supplier_id
FROM suppliers s
INNER JOIN (SELECT DISTINCT o.supplier_id FROM orders o) o
    ON o.supplier_id = s.supplier_id

SELECT s.supplier_id
FROM suppliers s
WHERE EXISTS (SELECT * FROM orders o WHERE o.supplier_id = s.supplier_id)

SELECT s.supplier_id 
FROM suppliers s 
WHERE s.supplier_id IN (SELECT o.supplier_id FROM orders o)

SELECT s.supplier_id
FROM suppliers s
INTERSECT
SELECT o.supplier_id
FROM orders o

1
คำตอบที่ดี แต่อย่าลืมว่าจะดีกว่าที่จะไม่ใช้เพื่อหลีกเลี่ยงความสัมพันธ์
Florian Fröhlich

1
คำถามใดที่คุณคิดว่าจะทำงานได้เร็วขึ้นหากซัพพลายเออร์มี 10M แถวและคำสั่งซื้อมี 100M แถวและทำไม
Teja

7

หากคุณมี where clause ที่มีลักษณะเช่นนี้:

WHERE id in (25,26,27) -- and so on

คุณสามารถเข้าใจได้อย่างง่ายดายว่าเหตุใดบางแถวจึงถูกส่งคืนและบางแถวไม่แสดง

เมื่ออนุประโยคเป็นดังนี้:

WHERE EXISTS (select * from orders where suppliers.supplier_id = orders.supplier_id);

มันหมายถึง: ส่งคืนแถวที่มีเรกคอร์ดที่มีอยู่ในตารางคำสั่งซื้อที่มี id เดียวกัน


2

แบบจำลองตารางฐานข้อมูล

สมมติว่าเรามีตารางสองตารางต่อไปนี้ในฐานข้อมูลของเราซึ่งสร้างความสัมพันธ์แบบตารางแบบหนึ่งต่อกลุ่ม

ตาราง SQL EXISTS

studentตารางเป็นแม่และstudent_gradeเป็นตารางเด็กเพราะมันมี student_id คอลัมน์สำคัญต่างประเทศอ้างอิง ID คอลัมน์คีย์หลักในตารางนักเรียน

student tableมีดังต่อไปนี้สองระเบียน:

| id | first_name | last_name | admission_score |
|----|------------|-----------|-----------------|
| 1  | Alice      | Smith     | 8.95            |
| 2  | Bob        | Johnson   | 8.75            |

และstudent_gradeตารางจะเก็บคะแนนที่นักเรียนได้รับ:

| id | class_name | grade | student_id |
|----|------------|-------|------------|
| 1  | Math       | 10    | 1          |
| 2  | Math       | 9.5   | 1          |
| 3  | Math       | 9.75  | 1          |
| 4  | Science    | 9.5   | 1          |
| 5  | Science    | 9     | 1          |
| 6  | Science    | 9.25  | 1          |
| 7  | Math       | 8.5   | 2          |
| 8  | Math       | 9.5   | 2          |
| 9  | Math       | 9     | 2          |
| 10 | Science    | 10    | 2          |
| 11 | Science    | 9.4   | 2          |

SQL EXISTS

สมมติว่าเราต้องการรับนักเรียนทุกคนที่ได้เกรด 10 ในชั้นเรียนคณิตศาสตร์

หากเราสนใจเฉพาะตัวระบุนักเรียนเราสามารถเรียกใช้แบบสอบถามเช่นนี้:

SELECT
    student_grade.student_id
FROM
    student_grade
WHERE
    student_grade.grade = 10 AND
    student_grade.class_name = 'Math'
ORDER BY
    student_grade.student_id

แต่แอปพลิเคชันสนใจที่จะแสดงชื่อเต็มของ a studentไม่ใช่แค่ตัวระบุดังนั้นเราจึงต้องการข้อมูลจากstudentตารางเช่นกัน

ในการกรองstudentระเบียนที่มีเกรด 10 ในวิชาคณิตศาสตร์เราสามารถใช้ตัวดำเนินการ EXISTS SQL ได้ดังนี้:

SELECT
    id, first_name, last_name
FROM
    student
WHERE EXISTS (
    SELECT 1
    FROM
        student_grade
    WHERE
        student_grade.student_id = student.id AND
        student_grade.grade = 10 AND
        student_grade.class_name = 'Math'
)
ORDER BY id

เมื่อเรียกใช้แบบสอบถามด้านบนเราจะเห็นว่ามีการเลือกเฉพาะแถว Alice:

| id | first_name | last_name |
|----|------------|-----------|
| 1  | Alice      | Smith     |

แบบสอบถามด้านนอกจะเลือกstudentคอลัมน์แถวที่เราสนใจกลับไปยังไคลเอนต์ อย่างไรก็ตามคำสั่ง WHERE ใช้ตัวดำเนินการ EXISTS กับแบบสอบถามย่อยภายในที่เกี่ยวข้อง

ตัวดำเนินการ EXISTS จะคืนค่า true หากเคียวรีย่อยส่งกลับอย่างน้อยหนึ่งเร็กคอร์ดและเป็นเท็จหากไม่มีการเลือกแถว เอ็นจินฐานข้อมูลไม่จำเป็นต้องเรียกใช้แบบสอบถามย่อยทั้งหมด หากมีการจับคู่ระเบียนเดียวตัวดำเนินการ EXISTS จะส่งกลับค่าจริงและเลือกแถวแบบสอบถามอื่นที่เกี่ยวข้อง

แบบสอบถามย่อยภายในมีความสัมพันธ์กันเนื่องจากคอลัมน์ student_id ของstudent_gradeตารางตรงกับคอลัมน์ id ของตารางนักเรียนชั้นนอก


เป็นอะไรที่ตอบโจทย์มาก ฉันคิดว่าฉันไม่ได้แนวคิดเพราะฉันใช้ตัวอย่างที่ไม่ถูกต้อง ไม่EXISTเพียง แต่ทำงานกับแบบสอบถามย่อยมีลักษณะร่วมกัน? ผมเล่นรอบกับแบบสอบถามที่มีเพียง 1 SELECT id FROM student WHERE EXISTS (SELECT 1 FROM student WHERE student.id > 1)ตารางเช่น ฉันรู้ว่าสิ่งที่ฉันเขียนสามารถทำได้โดยการสืบค้น WHERE ง่ายๆเพียงคำเดียว แต่ฉันแค่ใช้มันเพื่อทำความเข้าใจ EXISTS ผมมีทุกแถว แท้จริงแล้วเกิดจากการที่ฉันไม่ได้ใช้การสืบค้นย่อยที่สัมพันธ์กันหรือไม่? ขอบคุณ.
Bowen Liu

เหมาะสำหรับเคียวรีย่อยที่สัมพันธ์กันเท่านั้นเนื่องจากคุณต้องการกรองระเบียนของแบบสอบถามภายนอก ในกรณีของคุณสามารถแทนที่ข้อความค้นหาภายในด้วย WHERE TRUE
Vlad Mihalcea

ขอบคุณวลาด นั่นคือสิ่งที่ฉันคิดว่า. มันเป็นความคิดแปลก ๆ ที่เกิดขึ้นเมื่อฉันยุ่งกับมัน ฉันไม่รู้แนวคิดของแบบสอบถามย่อยที่สัมพันธ์กันโดยสุจริต และตอนนี้การกรองแถวของคิวรีภายนอกด้วยคิวรีภายในก็สมเหตุสมผลมากขึ้น
Bowen Liu

0

EXISTS หมายความว่าแบบสอบถามย่อยส่งคืนอย่างน้อยหนึ่งแถวนั่นคือสิ่งนั้นจริงๆ ในกรณีนั้นเป็นแบบสอบถามย่อยที่สัมพันธ์กันเนื่องจากตรวจสอบ supplier_id ของตารางด้านนอกไปยัง supplier_id ของตารางด้านใน แบบสอบถามนี้ระบุว่ามีผลบังคับใช้:

เลือกซัพพลายเออร์ทั้งหมดสำหรับรหัสซัพพลายเออร์แต่ละรายดูว่ามีคำสั่งซื้อสำหรับซัพพลายเออร์นี้หรือไม่หากซัพพลายเออร์ไม่อยู่ในตารางใบสั่งซื้อให้ลบซัพพลายเออร์ออกจากผลลัพธ์ส่งคืนซัพพลายเออร์ทั้งหมดที่มีแถวที่ตรงกันในตารางคำสั่งซื้อ

คุณสามารถทำสิ่งเดียวกันในกรณีนี้ด้วย INNER JOIN

SELECT suppliers.* 
  FROM suppliers 
 INNER 
  JOIN orders 
    ON suppliers.supplier_id = orders.supplier_id;

ความคิดเห็นของม้าถูกต้อง คุณต้องทำการจัดกลุ่มด้วยการเข้าร่วมนั้นหรือเลือกที่แตกต่างกันตามข้อมูลที่คุณต้องการ


4
การรวมภายในจะให้ผลลัพธ์ที่แตกต่างจาก EXISTS หากมีการเชื่อมโยงระเบียนย่อยกับผู้ปกครองมากกว่าหนึ่งระเบียนซึ่งไม่เหมือนกัน
OMG Ponies

ฉันคิดว่าความสับสนของฉันอาจเป็นเพราะฉันได้อ่านว่าการสืบค้นย่อยที่มี EXISTS ส่งคืนจริงหรือเท็จ แต่นี่ไม่ใช่สิ่งเดียวที่มันกลับมาใช่มั้ย? แบบสอบถามย่อยส่งคืน "ซัพพลายเออร์ที่มีแถวที่ตรงกันในตารางคำสั่งซื้อ" ทั้งหมดหรือไม่ แต่ถ้าเป็นเช่นนั้นคำสั่ง EXISTS จะส่งคืนผลลัพธ์แบบบูลีนอย่างไร ทุกสิ่งที่ฉันอ่านในหนังสือเรียนบอกว่ามันส่งคืนเฉพาะผลลัพธ์บูลีนเท่านั้นดังนั้นฉันจึงมีช่วงเวลาที่ยากลำบากในการกระทบยอดผลลัพธ์ของรหัสกับสิ่งที่ฉันได้รับแจ้งว่าจะส่งคืน
แดน

อ่าน EXISTS เหมือนฟังก์ชัน ... EXISTS (ชุดผลลัพธ์) จากนั้นฟังก์ชัน EXISTS จะคืนค่าจริงหากชุดผลลัพธ์มีแถวเท็จหากว่างเปล่า โดยพื้นฐานแล้ว
David Fells

3
@ แดนโปรดพิจารณาว่า EXISTS () ได้รับการประเมินอย่างมีเหตุผลสำหรับทุกแถวแหล่งที่มาโดยอิสระ - ไม่ใช่ค่าเดียวสำหรับการสืบค้นทั้งหมด
Arvo

-1

สิ่งที่คุณอธิบายคือคำค้นหาที่เรียกว่าแบบสอบถามย่อยที่สัมพันธ์กัน

(โดยทั่วไป) เป็นสิ่งที่คุณควรพยายามหลีกเลี่ยงโดยการเขียนแบบสอบถามโดยใช้การเข้าร่วมแทน:

SELECT suppliers.* 
FROM suppliers 
JOIN orders USING supplier_id
GROUP BY suppliers.supplier_id

เนื่องจากไม่เช่นนั้นการสืบค้นย่อยจะถูกเรียกใช้สำหรับแต่ละแถวในแบบสอบถามภายนอก


2
โซลูชันทั้งสองนี้ไม่เทียบเท่ากัน การเข้าร่วมให้ผลลัพธ์ที่แตกต่างจากแบบสอบถามย่อย EXISTS หากมีมากกว่าหนึ่งแถวในordersที่ตรงกับเงื่อนไขการรวม
a_horse_with_no_name

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