IN và EXISTS đều có thể dùng để kiểm tra một bản ghi có nằm trong một tập dữ liệu khác hay không.
Nhìn qua, hai cách này khá giống nhau. Nhưng cách SQL xử lý chúng và đặc biệt là vấn đề NULL có thể khiến kết quả khác nhau.
1. IN và EXISTS dùng để làm gì?
Giả sử có hai bảng:
customers
| id | name |
|---|---|
| 1 | An |
| 2 | Bình |
| 3 | Cường |
orders
| id | customer_id | amount |
|---|---|---|
| 101 | 1 | 500 |
| 102 | 1 | 300 |
| 103 | 2 | 700 |
Bạn muốn tìm những khách hàng đã từng đặt hàng.
Dùng IN
SELECT *
FROM customers
WHERE id IN (
SELECT customer_id
FROM orders
);
Có thể hiểu là:
Lấy khách hàng có
idnằm trong danh sáchcustomer_idcủa bảngorders.
Kết quả:
| id | name |
|---|---|
| 1 | An |
| 2 | Bình |
Dùng EXISTS
SELECT *
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
);
Có thể hiểu là:
Với mỗi khách hàng, kiểm tra xem có ít nhất một đơn hàng tương ứng hay không.
Kết quả cũng là:
| id | name |
|---|---|
| 1 | An |
| 2 | Bình |
2. Khác nhau ở cách tư duy
Đây là cách dễ nhớ nhất:
IN → kiểm tra giá trị có nằm trong tập hợp không
WHERE id IN (1, 2, 3)
Có thể hiểu:
idcó thuộc danh sách này không?
Với Subquery:
WHERE id IN (
SELECT customer_id
FROM orders
)
→ id có nằm trong tập giá trị mà Subquery trả về không?
EXISTS → kiểm tra có tồn tại bản ghi phù hợp không
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
)
→ Có ít nhất một order nào của khách hàng này không?
Điểm quan trọng:
EXISTS không quan tâm Subquery trả về giá trị gì. Nó chỉ quan tâm có tồn tại ít nhất một dòng thỏa điều kiện hay không.
Vì vậy:
SELECT 1
trong EXISTS là cách viết phổ biến. Bạn cũng có thể thấy:
SELECT *
nhưng giá trị được SELECT ra không phải thứ EXISTS dùng để quyết định kết quả.
3. EXISTS có thể phù hợp hơn khi Subquery lớn
Ví dụ bạn có bảng customers rất lớn và muốn tìm khách hàng có đơn hàng:
SELECT *
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
);
Về mặt logic, EXISTS chỉ cần biết:
“Có tồn tại ít nhất một dòng phù hợp không?”
Khi database tìm thấy dòng phù hợp, nó có thể không cần kiểm tra thêm các dòng khác trong Subquery.
Trong khi đó, với IN:
WHERE c.id IN (
SELECT customer_id
FROM orders
)
ta đang thể hiện logic:
“Giá trị này có nằm trong tập kết quả của Subquery không?”
Tuy nhiên, không nên kết luận rằng EXISTS luôn nhanh hơn IN.
Các hệ quản trị CSDL hiện đại có optimizer có thể biến đổi hai câu query thành những kế hoạch thực thi tương đương hoặc rất gần nhau.
Với Oracle, cách chính xác để biết query nào được thực thi tốt hơn là kiểm tra Execution Plan và dữ liệu thực tế.
4. Điểm dễ bị hỏi: NULL
Đây là phần rất đáng nhớ.
Giả sử:
customers
id
---
1
2
3
và:
orders
customer_id
-----------
1
2
NULL
Với:
SELECT *
FROM customers
WHERE id IN (
SELECT customer_id
FROM orders
);
Các giá trị 1 và 2 khớp.
Nhưng NULL có tính chất đặc biệt trong SQL: phép so sánh với NULL không cho kết quả TRUE như một giá trị thông thường.
Đây là lý do NOT IN đặc biệt dễ gây lỗi logic khi Subquery có thể trả về NULL.
Ví dụ:
SELECT *
FROM customers
WHERE id NOT IN (
SELECT customer_id
FROM orders
);
Nếu customer_id có NULL, kết quả có thể không giống với điều bạn trực giác mong đợi.
Trong trường hợp kiểm tra không tồn tại bản ghi liên quan, NOT EXISTS thường là cách viết an toàn và rõ nghĩa hơn:
SELECT *
FROM customers c
WHERE NOT EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
);
Ở đây ta đang hỏi trực tiếp:
Không tồn tại order nào của khách hàng này?
5. Vậy khi nào dùng IN, khi nào dùng EXISTS?
Không có quy tắc kiểu “EXISTS luôn tốt hơn IN”.
Có thể nhớ theo mục đích:
| Trường hợp | Cách viết phù hợp |
|---|---|
| Kiểm tra giá trị nằm trong một danh sách | IN |
| Danh sách giá trị nhỏ, rõ ràng | IN |
| Kiểm tra có bản ghi liên quan tồn tại | EXISTS |
| Subquery có quan hệ với dòng bên ngoài | EXISTS thường dễ diễn đạt hơn |
| Kiểm tra không tồn tại bản ghi liên quan | NOT EXISTS |
NOT IN với Subquery có thể chứa NULL | Cần đặc biệt cẩn thận |
Ví dụ:
Danh sách cố định:
SELECT *
FROM employees
WHERE department_id IN (10, 20, 30);
→ IN rất tự nhiên.
Kiểm tra quan hệ giữa hai bảng:
SELECT *
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.id
);
→ EXISTS thể hiện đúng ý “có tồn tại đơn hàng của khách hàng này”.
Tóm lại
IN → Giá trị có nằm trong tập hợp không?
EXISTS → Có tồn tại bản ghi phù hợp không?
NOT IN → Cẩn thận với NULL
NOT EXISTS → Thường rõ ràng hơn khi kiểm tra quan hệ "không tồn tại"
Điều quan trọng không phải là học thuộc EXISTS nhanh hơn IN, mà là hiểu bạn đang muốn kiểm tra giá trị thuộc một tập hợp hay sự tồn tại của một bản ghi liên quan.


