Chắc là bạn đã từng gặp biểu thức điều kiện trong các ngôn ngữ lập trình: if-else, switch-case và các kiểu tương tự. Trong SQL cũng có công cụ riêng để xử lý điều kiện: đó là biểu thức CASE. Nó cho phép bạn quyết định ngay trong truy vấn: nếu điều kiện đúng thì làm cái này, nếu không thì làm cái khác.
Cấu trúc CASE đặc biệt hữu ích khi bạn cần xử lý dữ liệu có thể chứa giá trị NULL. Cú pháp thì dễ như ăn kẹo, xem thử nhé:
CASE
WHEN điều_kiện1 THEN kết_quả1
WHEN điều_kiện2 THEN kết_quả2
...
ELSE kết_quả_mặc_định
END
Nghe hợp lý đúng không? "Nếu như thế này thì làm cái này, còn không thì làm cái kia". Dễ nhớ lắm: "WHEN là nếu, THEN là làm gì, ELSE là làm gì nếu không khớp cái nào".
Ví dụ: phân loại sản phẩm
Giả sử bạn có bảng products với cột price, và bạn muốn chia sản phẩm thành các nhóm dựa trên giá của chúng.
| id | name | price |
|---|---|---|
| 1 | Magic Wand | 120 |
| 2 | Potion Set | 45 |
| 3 | Crystal Ball | 75 |
| 4 | Enchanted Map | NULL |
| 5 | Broomstick | 99 |
| 6 | Spell Book | 180 |
SELECT
name AS product_name,
price,
CASE
WHEN price IS NULL THEN 'Không rõ'
WHEN price < 50 THEN 'Giá rẻ'
WHEN price BETWEEN 50 AND 100 THEN 'Tiêu chuẩn'
ELSE 'Cao cấp'
END AS price_category
FROM products;
Ở đây có gì?
- Nếu giá
pricecủa sản phẩm làNULL, ta hiển thị nhóm là'Không rõ'. - Nếu giá nhỏ hơn 50, sản phẩm được coi là
'Giá rẻ'. - Nếu giá nằm trong khoảng từ 50 đến 100, nó thuộc nhóm
'Tiêu chuẩn'. - Còn lại là hàng
'Cao cấp'.
Kết quả sẽ như sau:
| product_name | price | price_category |
|---|---|---|
| Magic Wand | 120 | Cao cấp |
| Potion Set | 45 | Giá rẻ |
| Crystal Ball | 75 | Tiêu chuẩn |
| Enchanted Map | NULL | Không rõ |
| Broomstick | 99 | Tiêu chuẩn |
| Spell Book | 180 | Cao cấp |
SQL như một phù thủy, đọc từng dòng trong bảng products, áp dụng cho từng dòng và phân loại cực kỳ thông minh.
Làm việc với NULL trong CASE WHEN
Rất thường xuyên, dữ liệu có giá trị bị thiếu (hello, NULL), và bạn cần thay thế nó bằng cái gì đó. Ví dụ, bạn có bảng users với cột email, và với những người không có email, bạn muốn hiển thị 'Chưa có'.
| user_id | name | |
|---|---|---|
| 1 | Alex Lin | alex@example.com |
| 2 | Maria Chi | NULL |
| 3 | Anna Song | anna@magic.com |
| 4 | Otto Art | NULL |
| 5 | John Smith | john@wizard.org |
SELECT
user_id,
name,
CASE
WHEN email IS NULL THEN 'Chưa có'
ELSE email
END AS email_address
FROM users;
| user_id | name | email_address |
|---|---|---|
| 1 | Alex Lin | alex@example.com |
| 2 | Maria Chi | Chưa có |
| 3 | Anna Song | anna@magic.com |
| 4 | Otto Art | Chưa có |
| 5 | John Smith | john@wizard.org |
SQL ở đây như một pháp sư, hồi sinh những dòng bị thiếu. Nếu email không có, nó thay bằng 'Chưa có'. Nếu có thì giữ nguyên.
Biểu thức điều kiện với số
Đôi khi bạn không chỉ thay thế giá trị mà còn muốn xây dựng logic mới. Ví dụ, giả sử bạn có bảng students với các cột score (điểm) và name.
| name | score |
|---|---|
| Alex Lin | 95 |
| Maria Chi | 82 |
| Anna Song | 48 |
| Otto Art | NULL |
| John Smith | 67 |
| Zoe Black | 30 |
Bạn muốn đánh giá sinh viên là "Xuất sắc", "Đạt" và "Trượt", tùy theo điểm số.
SELECT
name AS student_name,
score,
CASE
WHEN score IS NULL THEN 'Không có điểm'
WHEN score >= 90 THEN 'Xuất sắc'
WHEN score >= 50 THEN 'Đạt'
ELSE 'Trượt'
END AS performance_category
FROM students;
| student_name | score | performance_category |
|---|---|---|
| Alex Lin | 95 | Xuất sắc |
| Maria Chi | 82 | Đạt |
| Anna Song | 48 | Trượt |
| Otto Art | NULL | Không có điểm |
| John Smith | 67 | Đạt |
| Zoe Black | 30 | Trượt |
SQL chia sinh viên ra như một giám khảo khó tính: 90 điểm trở lên là "Xuất sắc", từ 50 là "Đạt". Còn thiếu điểm thì... bạn hiểu rồi đó.
Nhóm và xử lý NULL
Làm việc với nhóm dữ liệu là một lĩnh vực nữa mà CASE WHEN cực kỳ hữu ích. Giả sử bạn có bảng orders, và bạn muốn đếm tổng số đơn hàng theo trạng thái, kể cả đơn có trạng thái NULL.
| order_id | status |
|---|---|
| 1 | Completed |
| 2 | Pending |
| 3 | NULL |
| 4 | Shipped |
| 5 | Completed |
| 6 | NULL |
| 7 | Pending |
| 8 | Completed |
| 9 | Shipped |
| 10 | NULL |
SELECT
CASE
WHEN status IS NULL THEN 'Không trạng thái'
ELSE status
END AS order_status,
COUNT(*)
FROM orders
GROUP BY
CASE
WHEN status IS NULL THEN 'Không trạng thái'
ELSE status
END;
Truy vấn này xử lý gọn các trạng thái NULL và thay bằng 'Không trạng thái', đồng thời đếm tổng số đơn hàng trong từng nhóm. Kết quả như sau:
| order_status | count |
|---|---|
| Completed | 3 |
| Pending | 2 |
| Shipped | 2 |
| Không trạng thái | 3 |
Case thực tế: "ảo thuật" với CASE WHEN
Ví dụ 1: Sắp xếp có xét NULL
Đôi khi bạn muốn hiển thị giá trị NULL lên đầu hoặc cuối danh sách đã sắp xếp. Thường gặp trong danh sách task, nơi các task ưu tiên phải ở trên, còn task không ưu tiên (NULL) thì ở cuối.
| task_id | task_name | priority |
|---|---|---|
| 1 | Fix bugs | 1 |
| 2 | Update documentation | 3 |
| 3 | Plan sprint | NULL |
| 4 | Code review | 2 |
| 5 | Organize meeting | NULL |
| 6 | Deploy release | 1 |
SELECT
task_name,
priority,
CASE
WHEN priority IS NULL THEN 1
ELSE 0
END AS priority_sort
FROM tasks
ORDER BY priority_sort ASC, priority ASC;
Ở đây mình thêm một cột "ảo" priority_sort, giúp đẩy NULL xuống dưới cùng, còn lại thì sắp xếp tăng dần.
| task_name | priority | priority_sort |
|---|---|---|
| Deploy release | 1 | 0 |
| Fix bugs | 1 | 0 |
| Code review | 2 | 0 |
| Update documentation | 3 | 0 |
| Plan sprint | NULL | 1 |
| Organize meeting | NULL | 1 |
Ví dụ 2: Tính toán có xét NULL
Giờ thử tưởng tượng bạn tính tổng tiền đơn hàng trong bảng orders, cột discount có thể là NULL nếu không có giảm giá.
| order_id | total_price | discount |
|---|---|---|
| 101 | 100 | 10 |
| 102 | 200 | NULL |
| 103 | 150 | 15 |
| 104 | 120 | NULL |
| 105 | 80 | 5 |
Bạn cần thay NULL thành 0 để phép tính không bị lỗi.
SELECT
order_id,
total_price,
discount,
total_price -
CASE
WHEN discount IS NULL THEN 0
ELSE discount
END AS final_price
FROM orders;
| order_id | total_price | discount | final_price |
|---|---|---|---|
| 101 | 100 | 10 | 90 |
| 102 | 200 | NULL | 200 |
| 103 | 150 | 15 | 135 |
| 104 | 120 | NULL | 120 |
| 105 | 80 | 5 | 75 |
Trick nhỏ với CASE này giúp bạn yên tâm là NULL không phá hỏng phép toán.
Với orderid = 101: discount = 10. finalprice = 100 - 10 = 90. Với orderid = 102: discount = NULL. CASE trả về 0. finalprice = 200 - 0 = 200. Với orderid = 103: discount = 15. finalprice = 150 - 15 = 135. Với orderid = 104: discount = NULL. CASE trả về 0. finalprice = 120 - 0 = 120. Với orderid = 105: discount = 5. finalprice = 80 - 5 = 75.
Ví dụ 3: Hiển thị trạng thái người dùng
Trong công việc hàng ngày, bạn sẽ gặp bài toán hiển thị trạng thái người dùng (ví dụ "Hoạt động" hay "Chờ xác nhận") hoặc báo thiếu dữ liệu. Ví dụ, bảng users có cột last_login lưu ngày đăng nhập cuối.
| user_id | name | last_login |
|---|---|---|
| 1 | Alex Lin | 2024-12-10 |
| 2 | Maria Chi | 2025-04-20 |
| 3 | Anna Song | NULL |
| 4 | Otto Art | 2025-05-01 |
| 5 | Liam Park | 2025-05-25 |
SELECT
user_id,
name,
CASE
WHEN last_login IS NULL THEN 'Chưa từng đăng nhập'
WHEN last_login < CURRENT_DATE - INTERVAL '30 days' THEN 'Không hoạt động'
ELSE 'Hoạt động'
END AS user_status
FROM users;
Với truy vấn này, hệ thống admin sống động hơn: ai chưa từng đăng nhập thì là "Chưa từng đăng nhập", ai lâu không vào thì là "Không hoạt động". Còn lại là "Hoạt động"!
| user_id | name | user_status |
|---|---|---|
| 1 | Alex Lin | Không hoạt động |
| 2 | Maria Chi | Không hoạt động |
| 3 | Anna Song | Chưa từng đăng nhập |
| 4 | Otto Art | Không hoạt động |
| 5 | Liam Park | Hoạt động |
Lỗi phổ biến và cách tránh
Bỏ qua ELSE: Nếu bạn không thêm ELSE, SQL sẽ trả về NULL nếu không điều kiện nào đúng. Đôi khi không phải điều bạn muốn đâu. Tốt nhất là luôn ghi rõ ELSE, kể cả khi bạn nghĩ đã bao hết trường hợp.
CASE
WHEN condition THEN result
-- ELSE 'Giá trị mặc định' -- đừng quên nhé!
END
Điều kiện phức tạp không có ngoặc: Nếu bạn có nhiều điều kiện phức tạp với AND, OR hoặc NOT, luôn dùng ngoặc. Không có ngoặc là truy vấn của bạn sẽ "nghĩ" sai đấy.
CASE
WHEN (column1 IS NOT NULL AND column2 > 5) THEN 'Hợp lệ'
ELSE 'Không hợp lệ'
END
Làm việc với NULL: Nhớ là NULL không bao giờ bằng (=) cái gì cả. Ví dụ:
CASE
WHEN column = NULL THEN 'Sai rồi!' -- Lỗi!
WHEN column IS NULL THEN 'Chuẩn!' -- Đúng là như vậy.
END
GO TO FULL VERSION