CodeGym /Các khóa học /SQL SELF /Biểu thức điều kiện: CASE WHEN ... THEN ... ELSE ... END

Biểu thức điều kiện: CASE WHEN ... THEN ... ELSE ... END

SQL SELF
Mức độ, Bài học
Có sẵn

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ì?

  1. Nếu giá price của sản phẩm là NULL, ta hiển thị nhóm là 'Không rõ'.
  2. Nếu giá nhỏ hơn 50, sản phẩm được coi là 'Giá rẻ'.
  3. Nếu giá nằm trong khoảng từ 50 đến 100, nó thuộc nhóm 'Tiêu chuẩn'.
  4. 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 email
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
2
Nhiệm vụ
SQL SELF, mức độ, bài học
Đã khóa
Hiển thị điều kiện cho dữ liệu bị thiếu
Hiển thị điều kiện cho dữ liệu bị thiếu
2
Nhiệm vụ
SQL SELF, mức độ, bài học
Đã khóa
Gán danh mục dựa trên giá trị số
Gán danh mục dựa trên giá trị số
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION