Các câu lệnh SQL EXISTS hoạt động như thế nào?


88

Tôi đang cố gắng học SQL và đang gặp khó khăn trong việc hiểu các câu lệnh EXISTS. Tôi đã xem qua trích dẫn này về "tồn tại" và không hiểu điều gì đó:

Sử dụng toán tử tồn tại, truy vấn con của bạn có thể trả về không, một hoặc nhiều hàng và điều kiện chỉ cần kiểm tra xem truy vấn con có trả về bất kỳ hàng nào hay không. Nếu bạn nhìn vào mệnh đề select của truy vấn con, bạn sẽ thấy rằng nó bao gồm một chữ (1) duy nhất; vì điều kiện trong truy vấn chứa chỉ cần biết có bao nhiêu hàng được trả về, dữ liệu thực tế mà truy vấn con trả về là không liên quan.

Điều tôi không hiểu là làm thế nào để truy vấn bên ngoài biết truy vấn con đang kiểm tra hàng nào? Ví dụ:

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

Tôi hiểu rằng nếu id từ bảng nhà cung cấp và đơn hàng khớp nhau, truy vấn con sẽ trả về true và tất cả các cột từ hàng phù hợp trong bảng nhà cung cấp sẽ được xuất ra. Những gì tôi không hiểu là cách truy vấn con giao tiếp với hàng cụ thể nào (giả sử hàng có id nhà cung cấp 25) sẽ được in nếu chỉ trả về giá trị true hoặc false.

Đối với tôi, dường như không có mối quan hệ nào giữa truy vấn bên ngoài và truy vấn con.

Câu trả lời:


98

Nghĩ theo cách này:

Đối với 'mỗi' hàng từ Suppliers, hãy kiểm tra xem có 'tồn tại' một hàng trong Orderbảng đáp ứng điều kiện hay không Suppliers.supplier_id(điều này đến từ 'hàng' hiện tại của truy vấn bên ngoài) = Orders.supplier_id. Khi bạn tìm thấy hàng phù hợp đầu tiên, hãy dừng lại ngay tại đó - hàng WHERE EXISTSđã được thỏa mãn.

Liên kết kỳ diệu giữa truy vấn bên ngoài và truy vấn con nằm ở thực tế là nó Supplier_idđược chuyển từ truy vấn bên ngoài đến truy vấn con cho mỗi hàng được đánh giá.

Hay nói một cách khác, truy vấn con được thực thi cho mỗi hàng bảng của truy vấn bên ngoài.

Nó KHÔNG giống như truy vấn con được thực hiện trên toàn bộ và nhận được 'true / false' và sau đó cố gắng khớp điều kiện 'true / false' này với truy vấn bên ngoài.


7
Cảm ơn! "Nó KHÔNG giống như truy vấn con được thực thi trên toàn bộ và nhận 'true / false', sau đó cố gắng khớp điều kiện 'true / false' này với truy vấn ngoài." là những gì thực sự làm sáng tỏ nó đối với tôi, tôi vẫn nghĩ đó là cách hoạt động của các truy vấn con (và nhiều lần như vậy), nhưng những gì bạn nói có ý nghĩa vì truy vấn con dựa vào truy vấn bên ngoài và do đó phải được thực thi một lần mỗi hàng
Clarence Liu

32

Đối với tôi, dường như không có mối quan hệ nào giữa truy vấn bên ngoài và truy vấn con.

Bạn nghĩ mệnh đề WHERE bên trong ví dụ EXISTS đang làm gì? Làm thế nào để bạn đi đến kết luận đó khi tham chiếu SUPPLIERS không nằm trong mệnh đề FROM hoặc JOIN trong mệnh đề EXISTS?

EXISTS định giá TRUE / FALSE và thoát ra là TRUE trong lần kết hợp đầu tiên của tiêu chí - đây là lý do tại sao nó có thể nhanh hơn IN. Cũng lưu ý rằng mệnh đề SELECT trong EXISTS bị bỏ qua - IE:

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

... sẽ gặp lỗi chia cho 0, nhưng nó sẽ không. Mệnh đề WHERE là phần quan trọng nhất của mệnh đề TỒN TẠI.

Cũng lưu ý rằng JOIN không phải là sự thay thế trực tiếp cho EXISTS, vì sẽ có các bản ghi mẹ trùng lặp nếu có nhiều hơn một bản ghi con được liên kết với cha mẹ.


1
Tôi vẫn còn thiếu một cái gì đó. Nếu nó thoát ra ở lần so khớp đầu tiên, làm thế nào để kết quả cuối cùng là tất cả các kết quả trong đó o.supplierid = s.supplierid? Nó sẽ không chỉ xuất ra kết quả đầu tiên?
Dan

3
@Dan: Số lần EXISTSthoát, trả về TRUE trong lần so khớp đầu tiên - bởi vì nhà cung cấp tồn tại ít nhất một lần trong bảng ĐƠN HÀNG. Nếu bạn muốn thấy dữ liệu SUPPLIER trùng lặp do có nhiều hơn một mối quan hệ con trong ORDERS, bạn phải sử dụng JOIN. Nhưng hầu hết đều không muốn sự trùng lặp đó và việc chạy GROUP BY / DISTINCT có thể thêm chi phí vào truy vấn. EXISTShiệu quả hơn so với SELECT DISTINCT ... FROM SUPPLIERS JOIN ORDERS ...SQL Server, gần đây chưa thử nghiệm trên Oracle hoặc MySQL.
OMG Ponies

Tôi có một câu hỏi, việc đối sánh được thực hiện cho mọi bản ghi được CHỌN trong truy vấn bên ngoài. Cũng như vậy, chúng tôi tìm nạp từ Đơn hàng 5 lần nếu có 5 hàng được chọn từ Nhà cung cấp.
Rahul Kadukar

24

Bạn có thể tạo ra kết quả giống hệt nhau bằng cách sử dụng JOIN, EXISTS, IN, hoặc 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
câu trả lời tuyệt vời, nhưng cũng nhớ rằng nó là tốt hơn không sử dụng tồn tại để tránh sự tương quan
Florian Fröhlich

1
Bạn nghĩ truy vấn nào sẽ chạy nhanh hơn nếu nhà cung cấp có 10 triệu hàng và đơn đặt hàng có 100 triệu hàng và tại sao?
Teja

7

Nếu bạn có một mệnh đề where giống như thế này:

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

bạn có thể dễ dàng hiểu tại sao một số hàng được trả về và một số hàng thì không.

Khi mệnh đề where là như thế này:

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

nó chỉ có nghĩa là: trả về các hàng có bản ghi hiện có trong bảng đơn hàng với cùng một id.


2

Đây là một câu hỏi rất hay, vì vậy tôi quyết định viết một bài rất chi tiết về chủ đề này trên blog của mình.

Mô hình bảng cơ sở dữ liệu

Giả sử chúng ta có hai bảng sau trong cơ sở dữ liệu của mình, tạo thành mối quan hệ một-nhiều bảng.

Bảng SQL EXISTS

Các studentbảng là phụ huynh, và student_gradelà bảng con vì nó có một cột nước ngoài chính student_id tham khảo id cột khóa chính trong bảng học sinh.

Tập tin student tablechứa hai bản ghi sau:

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

Và, student_gradebảng lưu trữ điểm mà học sinh nhận được:

| 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 tồn tại

Giả sử chúng tôi muốn nhận tất cả học sinh được điểm 10 môn Toán.

Nếu chúng ta chỉ quan tâm đến mã định danh sinh viên, thì chúng ta có thể chạy một truy vấn như sau:

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

Tuy nhiên, ứng dụng quan tâm đến việc hiển thị tên đầy đủ của a student, không chỉ mã định danh, vì vậy chúng tôi cũng cần thông tin từ studentbảng.

Để lọc các studentbản ghi có điểm 10 môn Toán, chúng ta có thể sử dụng toán tử EXISTS SQL, như sau:

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

Khi chạy truy vấn ở trên, chúng ta có thể thấy rằng chỉ có hàng Alice được chọn:

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

Truy vấn bên ngoài chọn các studentcột hàng mà chúng tôi quan tâm để trả lại cho máy khách. Tuy nhiên, mệnh đề WHERE đang sử dụng toán tử EXISTS với một truy vấn con bên trong được liên kết.

Toán tử EXISTS trả về true nếu truy vấn con trả về ít nhất một bản ghi và false nếu không có hàng nào được chọn. Công cụ cơ sở dữ liệu không phải chạy hoàn toàn truy vấn con. Nếu một bản ghi được so khớp, toán tử EXISTS trả về true và hàng truy vấn liên quan khác được chọn.

Truy vấn con bên trong có tương quan vì cột student_id của student_gradebảng được so khớp với cột id của bảng sinh viên bên ngoài.


Thật là một câu trả lời tuyệt vời. Tôi nghĩ rằng tôi đã không hiểu được khái niệm bởi vì tôi đã sử dụng một ví dụ sai. Không EXISTchỉ làm việc với subquery tương quan? Tôi đang chơi với truy vấn chỉ chứa 1 bảng, như SELECT id FROM student WHERE EXISTS (SELECT 1 FROM student WHERE student.id > 1). Tôi biết những gì tôi đã viết có thể đạt được bằng một truy vấn WHERE đơn giản nhưng tôi chỉ sử dụng nó để hiểu TỒN TẠI. Tôi có tất cả các hàng. Có thực sự là do tôi không sử dụng truy vấn con tương quan không? Cảm ơn.
Bowen Liu

Nó chỉ có ý nghĩa đối với các truy vấn con tương quan khi bạn muốn lọc các bản ghi của truy vấn bên ngoài. Trong trường hợp của bạn, truy vấn bên trong có thể được thay thế bằng WHERE TRUE
Vlad Mihalcea

Cảm ơn Vlad. Đó là những gì tôi nghĩ. Đó chỉ là một ý tưởng kỳ lạ xảy ra khi tôi đang lộn xộn với nó. Thành thật mà nói, tôi không biết khái niệm về truy vấn con tương quan. Và bây giờ sẽ có ý nghĩa hơn nhiều khi lọc ra các hàng của truy vấn bên ngoài với truy vấn bên trong.
Bowen Liu

0

TỒN TẠI có nghĩa là truy vấn con trả về ít nhất một hàng, thực sự là vậy. Trong trường hợp đó, đó là một truy vấn con có tương quan vì nó kiểm tra cung cấp_id của bảng bên ngoài với cung cấp_id của bảng bên trong. Thực tế truy vấn này cho biết:

CHỌN tất cả nhà cung cấp Đối với mỗi ID nhà cung cấp, hãy xem liệu nhà cung cấp này có tồn tại đơn hàng hay không Nếu nhà cung cấp không có mặt trong bảng đơn hàng, hãy xóa nhà cung cấp khỏi kết quả QUAY LẠI tất cả nhà cung cấp có hàng tương ứng trong bảng đơn hàng

Bạn có thể làm điều tương tự trong trường hợp này với INNER JOIN.

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

Ponies nhận xét là chính xác. Bạn cần phải thực hiện việc nhóm với kết nối đó hoặc chọn riêng biệt tùy thuộc vào dữ liệu bạn cần.


4
Phép nối bên trong sẽ tạo ra các kết quả khác với EXISTS nếu nhiều bản ghi con được liên kết với một bản ghi mẹ - chúng không giống nhau.
OMG Ponies

Tôi nghĩ rằng sự nhầm lẫn của tôi có thể là tôi đã đọc rằng truy vấn con có EXISTS trả về true hoặc false; nhưng đây không thể là thứ duy nhất mà nó trả về, phải không? Có phải truy vấn con cũng trả về tất cả "nhà cung cấp có hàng tương ứng trong bảng đơn hàng" không? Nhưng nếu đúng như vậy thì câu lệnh EXISTS trả về kết quả boolean như thế nào? Mọi thứ tôi đang đọc trong sách văn bản đều nói rằng nó chỉ trả về một kết quả boolean, vì vậy tôi đang gặp khó khăn khi đối chiếu kết quả của mã với những gì tôi được thông báo là nó trả về.
Dan

Đọc EXISTS giống như một hàm ... EXISTS (tập kết quả). Sau đó, hàm EXISTS sẽ trả về true nếu tập kết quả có hàng, false nếu tập hợp trống. Về cơ bản là vậy.
David Fells

3
@Dan, hãy xem xét rằng EXISTS () được đánh giá logic cho mọi hàng nguồn một cách độc lập - nó không phải là giá trị duy nhất cho toàn bộ truy vấn.
Arvo

-1

Những gì bạn mô tả là một truy vấn được gọi là với một truy vấn con tương quan .

(Nói chung) đó là điều bạn nên tránh bằng cách viết truy vấn bằng cách sử dụng phép nối thay thế:

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

Bởi vì nếu không, truy vấn con sẽ được thực thi cho mỗi hàng trong truy vấn bên ngoài.


2
Hai giải pháp đó không tương đương. JOIN cho kết quả khác với truy vấn con EXISTS nếu có nhiều hơn một hàng trong ordersđó khớp với điều kiện nối.
a_horse_with_no_name

1
cảm ơn cho các giải pháp thay thế. nhưng bạn có gợi ý rằng nếu được đưa ra một tùy chọn giữa truy vấn con tương quan và phép nối, tôi nên chọn phép nối vì nó hiệu quả hơn không?
sunny_dev
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.