DHDataHọc
Lý thuyếtBài 11/306 phút đọc+10 XP

AND, OR và NULL

Bạn sẽ học được

  • Kết hợp nhiều điều kiện bằng AND, OR, NOT và kiểm soát thứ tự bằng ngoặc.
  • Hiểu vì sao AND được ưu tiên hơn OR và cách tránh nhầm lẫn.
  • Phân biệt NULL với chuỗi rỗng và số 0.
  • Dùng IS NULL, IS NOT NULL, COALESCE để xử lý dữ liệu thiếu.
  • Nhận ra bẫy NOT IN khi danh sách có NULL.

Khái niệm cốt lõi

Một điều kiện thường chưa đủ. "Khách ở Hà Nội và thuộc nhóm retail", "đơn đã hoàn tất hoặc đang chờ" — đó là lúc dùng AND và OR.

AND chỉ đúng khi cả hai vế đúng. OR đúng khi ít nhất một vế đúng. NOT đảo giá trị. Ba toán tử này tạo thành biểu thức luận lý, và cũng như phép nhân trước phép cộng trong số học, chúng có thứ tự ưu tiên: NOT trước, rồi AND, rồi OR.

Điều này khiến câu sau đây nguy hiểm:

code
SELECT name, city, segment
FROM customers
WHERE city = 'Hà Nội' AND segment = 'retail' OR city = 'Đà Nẵng';

Vì AND được tính trước, câu này tương đương:

code
WHERE (city = 'Hà Nội' AND segment = 'retail') OR city = 'Đà Nẵng'

Kết quả là An Nguyễn (Hà Nội, retail), Chi Lê và Linh Ngô (Đà Nẵng) — ba dòng. Nếu ý định là "khách Hà Nội hoặc Đà Nẵng, và đều thuộc retail", cần viết:

code
WHERE (city = 'Hà Nội' OR city = 'Đà Nẵng') AND segment = 'retail'

Kết quả vẫn là An, Chi, Linh — ba dòng trong trường hợp này, nhưng vì lý do khác: Dũng và Khánh ở Hà Nội nhưng thuộc wholesale, nên bị loại. Sự trùng hợp kết quả không chứng minh hai câu tương đương. Hãy thử đổi segment thành wholesale để thấy chúng tách nhau ngay.

Quy tắc thực hành: luôn đặt ngoặc khi trộn AND với OR, kể cả khi bạn chắc mình hiểu thứ tự ưu tiên. Ngoặc không làm truy vấn chậm; chúng làm ý định rõ ràng cho người đọc và cho chính bạn sáu tháng sau.

NULL là giá trị đặc biệt, nghĩa là "không có thông tin". Nó không phải chuỗi rỗng '', không phải số 0, và không bằng chính nó. Ba điều cần nhớ:

Biểu thứcKết quả
NULL = NULLunknown (không phải true)
NULL <> 5unknown
NULL AND trueunknown
NULL OR truetrue
NULL AND falsefalse

WHERE chỉ giữ các dòng cho true. Một dòng có email là NULL sẽ bị loại bởi cả email = 'an@example.com' lẫn email <> 'an@example.com'. Đây là lý do điều kiện "khác" không đếm được dữ liệu thiếu.

Muốn kiểm tra NULL, dùng toán tử chuyên dụng:

code
SELECT name FROM customers WHERE email IS NULL;       -- 2 dòng
SELECT name FROM customers WHERE email IS NOT NULL;   -- 10 dòng

Hai kết quả cộng lại bằng 12, đúng toàn bộ bảng. Phép kiểm tra này quan trọng: nếu bạn đếm khách "có email" bằng email <> '', bạn sẽ nhận 10 dòng nhưng vì lý do khác, và con số đó sẽ sai ngay khi có người nhập chuỗi rỗng.

Khi cần hiển thị giá trị thay thế, dùng COALESCE:

code
SELECT name, COALESCE(email, '(chưa cập nhật)') AS email
FROM customers
ORDER BY id;

COALESCE trả về đối số đầu tiên không NULL. Nó hữu ích cho báo cáo, nhưng đừng dùng nó để che dữ liệu thiếu trong câu WHERE — lọc và hiển thị là hai việc khác nhau.

Có một bẫy nâng cao với NOT IN: nếu danh sách chứa NULL, toàn bộ phép so sánh trở thành unknown, và truy vấn trả 0 dòng dù dữ liệu có. Ví dụ email NOT IN ('a@example.com', NULL) không trả về dòng nào. Cách an toàn là lọc NULL trước hoặc dùng NOT EXISTS. Với dữ liệu của khóa học, city không bao giờ NULL, nên city NOT IN ('Hà Nội', 'Đà Nẵng') chạy đúng — nhưng hãy nhớ quy tắc khi gặp cột cho phép NULL.

Ví dụ chạy được

Khách Hà Nội thuộc nhóm retail:

code
SELECT name, city, segment
FROM customers
WHERE city = 'Hà Nội' AND segment = 'retail';

Một dòng: An Nguyễn. Dũng Phạm và Khánh Hồ cũng ở Hà Nội nhưng thuộc wholesale, nên bị AND loại.

Khách ở Hà Nội hoặc Đà Nẵng:

code
SELECT name, city
FROM customers
WHERE city = 'Hà Nội' OR city = 'Đà Nẵng'
ORDER BY city, name;

Năm dòng: ba ở Hà Nội (An Nguyễn, Dũng Phạm, Khánh Hồ) và hai ở Đà Nẵng (Chi Lê, Linh Ngô).

Kết hợp phủ định:

code
SELECT name, city
FROM customers
WHERE NOT city = 'Hà Nội'
  AND NOT city = 'Đà Nẵng'
ORDER BY city;

Bảy dòng còn lại. Viết bằng city NOT IN ('Hà Nội', 'Đà Nẵng') gọn hơn và cho cùng kết quả, vì city là cột NOT NULL.

Tìm khách chưa có email ở Hà Nội:

code
SELECT name, email, city
FROM customers
WHERE email IS NULL AND city = 'Hà Nội';

Một dòng: Dũng Phạm. Inh Đỗ cũng thiếu email nhưng ở TP. Hồ Chí Minh.

Đếm để thấy ảnh hưởng của NULL:

code
SELECT COUNT(*) AS co_email FROM customers WHERE email IS NOT NULL;  -- 10
SELECT COUNT(*) AS khac_an  FROM customers WHERE email <> 'an@example.com';  -- 9

Dòng thứ hai trả 9, không phải 11. Trong 12 khách, hai người không có email nên không thể "khác an@example.com" — câu trả lời là "không biết", và WHERE loại luôn. Nếu muốn đếm cả người chưa có email, viết email IS NULL OR email <> 'an@example.com' và nhận 11.

Một truy vấn tổng hợp dùng cả ba toán tử:

code
SELECT name, city, segment
FROM customers
WHERE (city = 'Hà Nội' OR city = 'Đà Nẵng')
  AND segment = 'retail'
ORDER BY name;

Ba dòng: An Nguyễn, Chi Lê, Linh Ngô. Không có Dũng hay Khánh vì wholesale. Đây là dạng câu hỏi rất thường gặp trong phân tích: "trong nhóm khách này, những ai thuộc phân khúc kia".

Thực hành

  1. Viết truy vấn lấy khách thuộc nhóm vip ở TP. Hồ Chí Minh hoặc Cần Thơ. Dự đoán trước danh sách, rồi chạy. Sau đó bỏ ngoặc và giải thích kết quả thay đổi thế nào.
  2. Đếm số khách có email và số khách không có email. Hai số phải cộng lại bằng 12. Thử thay IS NULL bằng = NULL và giải thích vì sao kết quả là 0.
  3. Lấy các đơn completed có order_date từ tháng 6 trở đi. Kết quả bốn dòng. Viết lại bằng NOT (status <> 'completed' OR order_date < DATE '2025-06-01') và kiểm tra tính tương đương bằng De Morgan.
  4. Dùng COALESCE để hiển thị '(chưa có)' cho email thiếu, rồi lọc riêng hai dòng đó. So sánh với việc lọc bằng IS NULL trước khi hiển thị.

Lỗi thường gặp

Triệu chứngNguyên nhânCách sửa
Kết quả nhiều hơn dự kiến khi trộn AND/ORAND được ưu tiên hơn ORĐặt ngoặc quanh từng nhóm điều kiện
email <> '...' bỏ sót kháchNULL làm điều kiện thành unknownThêm OR email IS NULL nếu muốn tính cả
= NULL không trả dòng nàoNULL không bằng chính nóDùng IS NULL
NOT IN trả 0 dòng dù có dữ liệuDanh sách chứa NULLLoại NULL khỏi danh sách hoặc dùng NOT EXISTS
COALESCE che mất dữ liệu thiếuDùng thay thế ngay trong WHERELọc bằng IS NULL, chỉ dùng COALESCE khi hiển thị

Lỗi thứ nhất là lỗi tốn thời gian nhất vì kết quả vẫn "có vẻ đúng". Hãy tạo thói quen: mỗi khi câu truy vấn có cả AND và OR, đọc lại và tự hỏi "ngoặc ở đâu?". Nếu không trả lời được ngay, thêm ngoặc.

Tóm tắt

  • AND ưu tiên hơn OR; luôn dùng ngoặc khi trộn hai toán tử.
  • NOT đảo giá trị; có thể thay NOT IN bằng chuỗi điều kiện <> khi cần kiểm soát NULL.
  • NULL là "không biết", không bằng chính nó; dùng IS NULL / IS NOT NULL.
  • Điều kiện "khác" không đếm được dòng có NULL; cộng thêm IS NULL nếu cần.
  • COALESCE dùng khi hiển thị, không dùng để lách điều kiện lọc.

Dùng ← → để chuyển bài