Trong quá trình viết truy vấn dữ liệu, có một dạng sự cố dễ gây bối rối hơn cả việc gặp mã lỗi: câu lệnh thực thi thành công trong vài mili giây, cú pháp không sai, nhưng bảng kết quả trả về lại trống rỗng.
Tình huống này thường xuất hiện khi bạn cần lọc các bản ghi ở bảng này nhưng chưa từng phát sinh dữ liệu ở bảng kia. Về mặt cảm quan, dữ liệu thực tế vẫn nằm nguyên trong hệ thống và câu lệnh vẫn viết đúng ngữ nghĩa..
Khi rà soát lại từng lớp điều kiện, nguyên nhân cốt lõi thường nằm ở cách hệ thống xử lý logic khi vô tình chạm phải một giá trị NULL.
1. Một tình huống thực tế với mệnh đề NOT IN
Giả sử có hai bảng dữ liệu cơ bản trong một hệ thống bán hàng:
'customers': Danh sách thông tin khách hàng.
'orders': Danh sách các đơn hàng đã ghi nhận.
Yêu cầu đặt ra là: Lọc ra danh sách những khách hàng chưa từng phát sinh bất kỳ đơn hàng nào.
Một cách tiếp cận rất tự nhiên và ngắn gọn là dùng cấu trúc NOT IN:

Ngữ nghĩa của câu lệnh rất trực diện: lấy những khách hàng có customer_id không nằm trong danh sách các customer_id đã từng xuất hiện ở bảng orders.
Sau khi nhấn thực thi, màn hình trả về: 0 bản ghi.
Nếu kiểm tra riêng lẻ bảng customers, bạn thấy có hàng chục tài khoản mới vừa tạo tuần trước và rõ ràng chưa từng mua hàng. Vậy điều gì đã khiến câu lệnh loại bỏ toàn bộ các bản ghi này?
2. Cơ chế logic 3 giá trị (three-valued logic)
Để hiểu nguyên nhân, cần nhìn vào cách hệ thống cơ sở dữ liệu quan hệ diễn giải giá trị NULL.
Trong các công cụ bảng tính thông thường, người ta hay xem một ô trống tương đương với số 0 hoặc một chuỗi ký tự rỗng "". Nhưng trong SQL, NULL không phải là một giá trị. Nó là một ký hiệu trạng thái biểu thị sự vắng mặt của thông tin (missing / unknown data).
Vì NULL không mang giá trị xác định, các phép so sánh thông thường (=, <>, >, <) đối với nó đều không thể đưa ra câu trả lời đúng (TRUE) hoặc sai (FALSE). Kết quả của phép so sánh này luôn trả về trạng thái thứ ba: UNKNOWN (không xác định).
101 = NULL ➔ UNKNOWN
101 <> NULL ➔ UNKNOWN
NULL = NULL ➔ UNKNOWN
Hệ thống đánh giá điều kiện trong SQL vận hành theo hệ logic 3 giá trị (three-valued logic) gồm: TRUE, FALSE, và UNKNOWN.
3. Phân rã mệnh đề NOT IN
Khi câu lệnh NOT IN được thực thi, hệ thống sẽ phân rã mệnh đề đó thành một chuỗi các phép so sánh kết hợp bằng toán tử AND.
Giả sử bảng orders của bạn chỉ có 3 dòng dữ liệu, trong đó có một dòng thử nghiệm nội bộ bị bỏ trống trường khách hàng:
customer_id gồm các giá trị: (101, 102, NULL).
Lúc này, điều kiện:

sẽ được diễn giải tương đương thành:

Xem xét từng bước đánh giá:
Giả sử xét khách hàng mới có customer_id = 200.
Biểu thức (200 <> 101) trả về TRUE.
Biểu thức (200 <> 102) trả về TRUE.
Nhưng đến biểu thức cuối cùng: (200 <> NULL), kết quả trả về là UNKNOWN.
Theo quy tắc toán học của toán tử logic AND:

Chỉ cần một vế trong chuỗi AND mang kết quả UNKNOWN, toàn bộ chuỗi điều kiện sẽ trả về UNKNOWN.
4. Nguyên tắc vận hành của mệnh đề WHERE:
Hệ thống chỉ cho phép các bản ghi đi qua nếu điều kiện lọc đánh giá chính xác là TRUE. Mọi bản ghi có kết quả đánh giá là FALSE hoặc UNKNOWN đều bị loại bỏ.
Do điều kiện lọc của mọi dòng dữ liệu đều bị rơi vào trạng thái UNKNOWN, toàn bộ tập dữ liệu bị loại bỏ hoàn toàn, dẫn đến kết quả 0 dòng.
5. Hai phương án xử lý trong công việc thực tế
Khi đã hiểu bản chất đánh giá logic của hệ thống, bạn có thể kiểm soát hoàn toàn tình huống này bằng hai cách:
Cách 1: Chủ động loại trừ giá trị NULL trong truy vấn con
Nếu vẫn giữ thói quen sử dụng NOT IN, cần bổ sung một tầng bảo vệ để đảm bảo danh sách trả về không bị thiếu bất kỳ giá trị nào thỏa mãn điều kiện:

Khi cột dữ liệu được lọc sạch các giá trị NULL, chuỗi so sánh AND chỉ còn lại hai giá trị TRUE hoặc FALSE, giúp câu truy vấn trả về đúng các khách hàng chưa mua hàng.
Cách 2: Chuyển sang sử dụng mệnh đề NOT EXISTS
Trong thực tế, NOT EXISTS thường là phương pháp được ưu tiên để xử lý việc loại trừ tập hợp dữ liệu. Nó giúp tránh được vấn đề với giá trị NULL một cách tự nhiên và thường mang lại hiệu năng tốt hơn

Trong mệnh đề EXISTS, Database Engine không quan tâm dữ liệu trả về là cột gì hay giá trị bao nhiêu, nó chỉ kiểm tra xem có tồn tại ít nhất một dòng thỏa mãn điều kiện hay không. Việc đặt SELECT 1 (hoặc bất kỳ giá trị hằng số nào) bên trong truy vấn con ở đây là một thói quen viết phổ biến để biểu thị rằng chúng ta chỉ cần xác nhận sự hiện diện của bản ghi mà không cần nạp thêm dữ liệu cột vào bộ nhớ.
Khác với NOT IN, mệnh đề NOT EXISTS không thực hiện việc bung chuỗi so sánh giá trị rời rạc. Nó chỉ kiểm tra sự hiện diện của bản ghi (có dòng nào ở bảng orders khớp với customer_id hiện tại hay không).
Nếu không tìm thấy bản ghi nào thỏa mãn, mệnh đề trả về TRUE. Cơ chế này hoàn toàn miễn nhiễm với sự xuất hiện của các giá trị NULL trong bảng được tham chiếu trong truy vấn con.
Việc quan sát cách hệ thống vận hành với giá trị NULL cho thấy: trong cơ sở dữ liệu quan hệ, dữ liệu trống không đơn thuần là một khoảng trắng hiển thị trên báo cáo. Nó là một trạng thái logic có khả năng thay đổi hoàn toàn kết quả của một câu truy vấn nếu không được kiểm soát chặt chẽ ngay từ bước thiết kế điều kiện lọc.

Bình luận
0 bình luậnViết bình luận
Chưa có bình luận nào. Hãy bắt đầu cuộc trò chuyện.