Câu 1SELECT * FROM customer WHERE email = NULL; trả về gì?
WHERE đào sâu ③ — IS NULL và IN cho lọc NULL và tập hợp
Bài đào sâu WHERE thứ ba — lọc trên NULL và trên tập hợp. Bao gồm logic ba giá trị, IS NULL / IS NOT NULL, IN / NOT IN, và bẫy NOT IN + NULL, tất cả trên tập dữ liệu customer nạp từ CSV.
Dữ liệu sẽ dùng — bảng customer (có NULL)
Bài đào sâu WHERE thứ 3 trình bày NULL và lọc theo tập hợp. Chúng ta sẽ làm việc với bảng customer (8 hàng) nạp từ CSV, trong đó các cột age / email / country mỗi cột đều có hàng với NULL (giá trị chưa biết). Trong các bảng thực tế, việc các cột không bắt buộc xen kẽ NULL là chuyện bình thường — "email chưa đăng ký," "country để trống," v.v.
Năm bài tập trong bài này đi qua, theo thứ tự: cách đúng để lấy NULL (IS NULL / IS NOT NULL), khớp danh sách ứng viên (IN / NOT IN), và cuối cùng là bẫy dễ bỏ sót khi dùng NOT IN trên dữ liệu chứa NULL.
NULL và logic ba giá trị — true, false, unknown
Trong hầu hết các ngôn ngữ lập trình, một điều kiện hoặc là TRUE hoặc là FALSE — hai lựa chọn. SQL thêm một trạng thái nữa: NULL (chưa biết). Đây gọi là logic ba giá trị. NULL không có nghĩa là "giá trị trống"; nó nghĩa là "giá trị không được biết." Kể cả NULL = NULL cũng đánh giá thành NULL (chưa biết) — so sánh hai giá trị chưa biết để lại kết quả chưa biết.
WHERE condition chỉ giữ các hàng mà điều kiện là TRUE. Cả hàng NULL và FALSE đều bị loại trừ. Khi quy tắc đó vào đầu, bạn sẽ thấy tại sao WHERE email = NULL luôn trả về rỗng.
Tại sao `email = NULL` không trả về gì cả
So sánh email = NULL luôn trả về NULL (chưa biết) khi một trong hai vế là NULL. 'unknown' = NULL và NULL = NULL đều đánh giá thành NULL. WHERE chỉ giữ các hàng mà điều kiện là TRUE, nên các điều kiện NULL bị loại trừ, để lại 0 hàng.
Để lấy NULL, bạn cần cú pháp dành riêng column IS NULL / column IS NOT NULL. Những cái này không làm so sánh bằng — chúng kiểm tra trực tiếp xem giá trị có phải là NULL không — và kết quả luôn là TRUE hoặc FALSE.
"Không có khách hàng nào chưa rời bỏ"?
Khi lấy "số lượng khách hàng chưa rời bỏ (deleted_at IS NULL)," tôi từng viết SELECT COUNT(*) FROM user WHERE deleted_at = NULL; và kết quả trả về là 0.
Thực tế có hàng ngàn người dùng đang hoạt động, nhưng deleted_at = NULL luôn đánh giá thành NULL (chưa biết) và bị WHERE loại trừ, nên dù có bao nhiêu người dùng kết quả cũng luôn là 0. Đó là lỗi dễ mắc khi mới bắt đầu với SQL — hãy hình thành thói quen dùng `IS NULL` / `IS NOT NULL` cho mọi kiểm tra NULL.
IS NULL và IS NOT NULL — lấy các giá trị chưa biết
Để lấy các hàng có giá trị là NULL (chưa biết), dùng `column IS NULL`. Để lấy các hàng không phải NULL, dùng column IS NOT NULL. Chỉ cần nhớ không bao giờ dùng `= NULL` hoặc `<> NULL` và bạn sẽ né được bẫy logic ba giá trị.
Như đoạn mã dưới đây cho thấy, cú pháp chỉ là IS thay vì = — nhưng kết quả hoàn toàn khác. = NULL không trả về gì; IS NULL trả về đúng các hàng NULL.
-- 1) NG: = NULL không lấy được gì (luôn NULL = loại trừ)
SELECT name, email FROM customer WHERE email = NULL;
-- Kết quả: 0 hàng
-- 2) OK: IS NULL lấy các hàng NULL
SELECT name, email FROM customer WHERE email IS NULL;
-- Kết quả: 4 hàng (Bob / Dave / Frank / Henry)
-- 3) Lấy các hàng không NULL
SELECT name, email FROM customer WHERE email IS NOT NULL;
-- Kết quả: 4 hàng (Alice / Carol / Eve / Grace)
IN và NOT IN — khớp với danh sách ứng viên
Khi bạn muốn kiểm tra xem giá trị của một cột có nằm trong danh sách ứng viên — "country là Japan, US, hoặc UK" — đó là IN. Viết column IN (value1, value2, ...) trả về các hàng mà cột khớp với bất kỳ ứng viên nào. Nó nghĩa giống như column = value1 OR column = value2 OR column = value3; mọi người chọn IN thường chỉ vì khả năng đọc.
Thêm NOT IN trả về các hàng không khớp với bất kỳ ứng viên nào.
-- 1) IN: khớp ứng viên bất kỳ
SELECT name, country FROM customer
WHERE country IN ('Japan', 'US');
-- Cùng nghĩa, viết với OR
SELECT name, country FROM customer
WHERE country = 'Japan' OR country = 'US';
-- 2) NOT IN: không khớp ứng viên nào
SELECT name, country FROM customer
WHERE country NOT IN ('Japan');
Bẫy `NOT IN` + NULL
NOT IN ('Japan') trông như lấy "các khách hàng có country không phải Japan," nhưng nó cũng loại trừ các hàng mà `country` là NULL. Đó là vì country NOT IN ('Japan') nội bộ đánh giá là country <> 'Japan' (không bằng), và NULL <> 'Japan' trả về NULL (chưa biết). NULL không phải TRUE, nên WHERE loại bỏ hàng.
Đi xa hơn, viết NOT IN ('Japan', NULL) — với một NULL trong danh sách ứng viên — khiến mọi hàng đánh giá thành NULL và biến mất. Khi dùng NOT IN trên một cột có thể chứa NULL, luôn làm cho xử lý NULL rõ ràng (column NOT IN (...) AND column IS NOT NULL) hoặc viết lại truy vấn theo IN.
Kiểm tra kiến thức
Hãy trả lời từng câu hỏi một.
Câu 2Câu nào sau đây mô tả đúng lọc với IN?
Câu 3Chạy WHERE country NOT IN ('Japan') trên một bảng customer mà cột country chứa NULL, các hàng country-NULL được xử lý ra sao?