VISEN GROUP
Technology & Engineering

IN và EXISTS: Khác nhau thế nào và khi nào nên dùng?

September 21, 20265 min readUncategorized

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

idname
1An
2Bình
3Cường

orders

idcustomer_idamount
1011500
1021300
1032700

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ó id nằm trong danh sách customer_id của bảng orders.

Kết quả:

idname
1An
2Bì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à:

idname
1An
2Bì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:

id có 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ợpCách viết phù hợp
Kiểm tra giá trị nằm trong một danh sáchIN
Danh sách giá trị nhỏ, rõ ràngIN
Kiểm tra có bản ghi liên quan tồn tạiEXISTS
Subquery có quan hệ với dòng bên ngoàiEXISTS thường dễ diễn đạt hơn
Kiểm tra không tồn tại bản ghi liên quanNOT EXISTS
NOT IN với Subquery có thể chứa NULLCầ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.

Leave a Reply

Your email address will not be published. Required fields are marked *