Lập trình · 24/09/2026

NULL trong SQL: Những bẫy khi lọc và đếm dữ liệu

Bộ lọc “không giao cho nhân viên số 7” trả về ít công việc hơn dự kiến dù truy vấn không báo lỗi. Nguyên nhân có thể là những dòng chưa có người phụ trách mang giá trị NULL. Trong SQL, chưa biết không đồng nghĩa với khác, bằng 0 hay chuỗi rỗng.

NULL trong SQL: Những bẫy khi lọc và đếm dữ liệu

Bộ lọc “không giao cho nhân viên số 7” trả về ít công việc hơn dự kiến dù truy vấn không báo lỗi. Nguyên nhân có thể là những dòng chưa có người phụ trách mang giá trị NULL. Trong SQL, chưa biết không đồng nghĩa với khác, bằng 0 hay chuỗi rỗng.

Bài viết dùng PostgreSQL với giá trị vô hướng, tập trung vào ý nghĩa điều kiện lọc. Ví dụ chỉ dùng SELECT trên dữ liệu minh họa, không tạo bảng hoặc sửa dữ liệu production. Cần kiểm thử lại trên hệ quản trị thực tế nếu chuyển sang SQL dialect khác.

1. Vì sao dấu khác không lấy được mọi dòng còn lại?

Tài liệu PostgreSQL giải thích rằng phép so sánh thông thường với NULL cho kết quả unknown. Điều kiện WHERE chỉ giữ dòng có kết quả true. Vì thế assignee_id <> 7 không nhận các dòng mà assignee_id là NULL.

Trước khi sửa truy vấn, làm rõ nghiệp vụ: “không phải nhân viên 7” có bao gồm công việc chưa phân công không? Nếu có, dùng điều kiện nêu rõ trường hợp đó. Nếu chỉ muốn các công việc đã giao cho người khác, phép so sánh thông thường có thể đúng ý định.

2. Kiểm tra NULL bằng đúng biểu thức

-- Unassigned tasks:
WHERE assignee_id IS NULL

-- Assigned tasks:
WHERE assignee_id IS NOT NULL

-- Someone else, or unassigned:
WHERE assignee_id <> 7 OR assignee_id IS NULL

Đây là các mảnh điều kiện thay thế nhau, không phải một câu SQL hoàn chỉnh. Không viết = NULL hoặc != NULL để tìm dòng thiếu dữ liệu. Cũng đừng sửa cấu hình database để che cú pháp sai thay vì làm rõ truy vấn.

3. Khi cần so sánh có xử lý NULL rõ ràng

PostgreSQL hỗ trợ IS DISTINCT FROM: hai giá trị khác nhau, hoặc một bên NULL và bên kia có giá trị, cho true. Hai bên cùng NULL cho false. Dạng ngược lại là IS NOT DISTINCT FROM.

WITH tickets(id, assignee_id) AS (
    VALUES (1, 7), (2, NULL::integer), (3, 9)
)
SELECT id
FROM tickets
WHERE assignee_id IS DISTINCT FROM 7
ORDER BY id;

Kết quả kỳ vọng là ID 2 và 3. Nếu thay điều kiện bằng assignee_id <> 7, chỉ ID 3 được nhận. Bộ dữ liệu nhỏ này giúp người review nhìn thấy khác biệt ngay, trước khi áp dụng vào bảng có nhiều dữ liệu và điều kiện phân quyền.

4. Cẩn thận với NOT IN

Tài liệu biểu thức subquery lưu ý ảnh hưởng của NULL với NOT IN. Chẳng hạn, 9 NOT IN (7, NULL) không cho true. Nếu danh sách loại trừ lấy từ một cột nullable, kết quả có thể khác hoàn toàn với cách đọc bằng ngôn ngữ tự nhiên.

Có thể cân nhắc NOT EXISTS với điều kiện tương quan, nhưng phải quyết định cả cách xử lý NULL ở phía dữ liệu chính. Không thay NOT IN bằng NOT EXISTS một cách máy móc rồi cho rằng chúng luôn tương đương. Viết test cho danh sách rỗng, có NULL, có giá trị trùng và trường hợp giá trị bên trái cũng NULL.

5. COUNT cũng cần đúng câu hỏi

Theo tài liệu aggregate, COUNT(*) đếm dòng, còn COUNT(assignee_id) đếm dòng mà biểu thức đó không NULL. Với ba công việc ở ví dụ, hai kết quả lần lượt là 3 và 2.

Nếu dashboard ghi “tổng công việc” nhưng dùng COUNT trên cột người phụ trách, công việc chưa phân công có thể biến mất khỏi thống kê. Đây là lỗi về định nghĩa chỉ số, không chỉ là lỗi viết SQL.

6. Không lấp mọi khoảng trống bằng số 0

Đề xuất thiết kế: ghi rõ NULL ở mỗi trường nghĩa là chưa biết, chưa áp dụng hay chưa được nhập. Nếu các trạng thái này có ý nghĩa nghiệp vụ khác nhau, cân nhắc trường trạng thái riêng. Đừng đổi tất cả NULL thành 0 hoặc chuỗi rỗng chỉ để truy vấn ngắn hơn: cách đó có thể xóa thông tin cần thiết.

  • Test ít nhất một dòng có giá trị, một dòng NULL và tập kết quả rỗng.
  • Đối chiếu bộ lọc giao diện với định nghĩa nghiệp vụ.
  • Kiểm tra dữ liệu nullable trong danh sách loại trừ.
  • Phân biệt đếm dòng với đếm giá trị đã có.
  • Giữ điều kiện tenant và quyền truy cập khi sửa bộ lọc.

Xử lý NULL tốt bắt đầu từ việc mô hình hóa điều chưa biết. Khi ý nghĩa dữ liệu rõ ràng, lựa chọn toán tử và ca kiểm thử sẽ dễ thống nhất hơn giữa backend, giao diện và báo cáo.

Thảo luận

Bình luận 0

Đăng nhập để bình luận

Bạn cần có tài khoản để tham gia thảo luận và trả lời độc giả khác.

Đăng nhậpĐăng ký

Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.