CASE WHEN – Kẻ thù hay người bạn của bạn trong SQL

 CASE WHEN – rẽ nhánh đúng, query sống; rẽ sai, pipeline gãy.

“Query này chỉ thêm 1 CASE thôi mà…” Hai tháng sau, bạn ngồi fix bug trong đống CASE WHEN chồng chéo 600 dòng.
Câu chuyện quen thuộc của bất kỳ ai từng vận hành hệ thống BI, reconcile hay KPI dashboard.

1. Câu chuyện bắt đầu: từ một CASE nhỏ

Tháng 5/2024, team BI được yêu cầu cập nhật rule commission mới:

Shopee: 3% với đơn < 500k, 6% với đơn ≥ 500k
Lazada: vẫn giữ 4%
TikTok: 5%, nhưng nếu seller tier A thì giảm 1%

Bạn mở SQL lên, và bắt đầu:

CASE
  WHEN platform = 'Shopee' AND booking_value < 500000 THEN booking_value * 0.03
  WHEN platform = 'Shopee' AND booking_value >= 500000 THEN booking_value * 0.06
  WHEN platform = 'Lazada' THEN booking_value * 0.04
  WHEN platform = 'TikTok' AND seller_tier = 'A' THEN booking_value * 0.04
  WHEN platform = 'TikTok' THEN booking_value * 0.05
END AS commission_fee

Chạy thử — ra kết quả đúng.
Push vào Airflow — chạy ổn.
Dashboard cập nhật, ai cũng vui.

Cho đến tháng sau:

  • Shopee thêm loại đơn “voucher subsidy” cần cộng thêm 0.5%.
  • Lazada có chương trình đặc biệt “free shipping weekend”.
  • TikTok đổi cấu trúc seller tier từ A/B/C thành numeric.

    Và bạn bắt đầu thấy mình copy-paste thêm 10 dòng CASE WHEN mới.
    Query 100 dòng biến thành 500 dòng.
    Và pipeline lại fail khi một ELSE quên đóng ngoặc.

    2. Khi CASE WHEN trở thành cái bẫy

    CASE WHEN là công cụ tuyệt vời – giúp bạn xử lý logic ngay trong SQL.
    Nhưng nó không sinh ra để chứa business rule phức tạp.

    Dấu hiệu “CASE bị lạm dụng”:

    • Query > 300 dòng, trong đó 70% là CASE WHEN.
    • Rule thay đổi → phải sửa ở nhiều nơi khác nhau.
    • Developer mới đọc vào… không dám đụng.
    • Không thể test từng rule riêng biệt.

      CASE WHEN là procedural logic viết trong declarative language
      SQL được thiết kế để “nói bạn muốn gì”, không phải “nói làm sao để làm nó”.

      3. Cách viết CASE hiệu quả hơn (và sạch hơn)


      3.1. Viết theo hướng "fail fast" – cái dễ, rõ, loại trừ trước

      Thay vì lồng CASE WHEN hoặc viết chồng lên nhau, hãy xử lý điều kiện dễ check nhất trước.

      CASE
        WHEN order_status IS NULL THEN 'Invalid'
        WHEN payment_status = 'Failed' THEN 'Mismatch'
        WHEN commission_fee IS NULL THEN 'Missing Fee'
        ELSE 'Valid'
      END AS flag_status
      
      • Dễ đọc hơn.
      Khi thêm rule mới, chỉ thêm 1 dòng ở đầu là đủ.
      • Tư duy “điều gì sai nhất” luôn đứng trước — rất hợp với reconcile logic.

        3.2. Gom nhóm CASE WHEN bằng bảng trung gian

        Nếu bạn thấy CASE của mình bắt đầu lặp logic,
        hãy đưa rule ra một bảng riêng để join.

        Ví dụ:
        platform fee_type rate
        Shopee normal 0.05
        Shopee promo 0.03
        Lazada normal 0.04
        TikTok normal 0.05

        Thay vì viết CASE:

        CASE
          WHEN platform = 'Shopee' AND is_promo THEN 0.03
          WHEN platform = 'Shopee' THEN 0.05
          WHEN platform = 'Lazada' THEN 0.04
          WHEN platform = 'TikTok' THEN 0.05
        END AS rate
        

        Bạn chỉ cần:

        SELECT o.*, r.rate
        FROM orders o
        JOIN config_rate r
          ON o.platform = r.platform
          AND o.fee_type = r.fee_type;
        

        Giờ thay đổi rule chỉ cần update bảng config,
        không cần deploy lại code.

        3.3. Chia CASE lớn thành nhiều CASE nhỏ

        Nếu logic gồm nhiều phần (commission, shipping, subsidy...),
        hãy tách ra từng cột riêng.

        CASE
          WHEN platform = 'Shopee' THEN nmv * 0.05
          WHEN platform = 'Lazada' THEN nmv * 0.04
        END AS commission_fee,
        
        CASE
          WHEN platform = 'Shopee' THEN -15000
          WHEN platform = 'Lazada' THEN -12000
        END AS shipping_subsidy
        

        Đừng gộp tất cả trong một CASE “đa năng” chỉ vì muốn gọn —
        đến khi debug, bạn sẽ hối hận.

        3.4. Sử dụng function khi logic lặp đi lặp lại

        Giả sử trong 5 query khác nhau, bạn đều phải check:

        CASE
          WHEN flag_platform_order != 'Matched' THEN 'Mismatch'
          WHEN flag_wallet != 'Matched' THEN 'Mismatch'
          WHEN flag_nmv != 'Matched' THEN 'Mismatch'
          ELSE 'Matched'
        END AS flag_all
        

        Thì hãy viết function:

        CREATE OR REPLACE FUNCTION f_flag_all(
            flag_platform_order TEXT,
            flag_wallet TEXT,
            flag_nmv TEXT
        )
        RETURNS TEXT AS $$
        BEGIN
            IF flag_platform_order != 'Matched' THEN
                RETURN 'Mismatch';
            ELSIF flag_wallet != 'Matched' THEN
                RETURN 'Mismatch';
            ELSIF flag_nmv != 'Matched' THEN
                RETURN 'Mismatch';
            END IF;
            RETURN 'Matched';
        END;
        $$ LANGUAGE plpgsql;
        

        Và gọi gọn lại:

        SELECT f_flag_all(flag_platform_order, flag_wallet, flag_nmv);
        


        4. Một case thực tế – Reconcile rule Shopee

        Trước khi tối ưu:

        CASE
          WHEN platform = 'Shopee' AND flag_platform_order != 'Matched' THEN 'Mismatch'
          WHEN platform = 'Shopee' AND flag_nmv != 'Matched' THEN 'Mismatch'
          WHEN platform = 'Shopee' AND flag_wallet != 'Matched' THEN 'Mismatch'
          ELSE 'Matched'
        END AS flag_all
        

        Sau khi tối ưu:

        Tách điều kiện platform ra ngoài, và dùng function cho phần flag.

        CASE
          WHEN platform = 'Shopee'
            THEN f_flag_all(flag_platform_order, flag_wallet, flag_nmv)
          ELSE flag_nmv
        END AS flag_all
        

        Cấu trúc SQL trở nên rõ ràng, có tầng, và dễ kiểm soát:

        • Query chỉ chứa logic join + select.
        • Function chứa rule xử lý flag.
        • Config chứa rule platform-specific.

          5. CASE WHEN không xấu – chỉ là nó đang làm việc sai chỗ

          CASE WHEN tuyệt vời nếu bạn:

          • cần mapping nhanh (ví dụ region code, channel, flag đơn giản),
          • cần tính toán tạm thời trong view,
          • cần derive cột nhỏ (ví dụ phân nhóm giá trị, bucket hóa).

            Nhưng nó sai chỗ nếu:

            • bạn dùng để định nghĩa rule tài chính,
            • hoặc chứa logic nghiệp vụ thay đổi thường xuyên.
            c Một nguyên tắc dễ nhớ:
            Nếu rule có thể thay đổi, hãy tách nó ra khỏi query.
            CASE chỉ nên phục vụ cho việc đọc dữ liệu, không phải mô phỏng hệ thống.

            6. Cách viết CASE “sạch” – checklist cho data team

            Mục tiêu Cách làm
            Query ngắn Chia CASE theo cột, không gộp nhiều tầng
            Dễ bảo trì Function hóa logic phức tạp
            Rule thay đổi Dùng bảng config hoặc file YAML/CSV
            Debug nhanh Check điều kiện sai trước, return sớm
            Hiệu năng Tránh dùng CASE trong JOIN condition, thay bằng mapping table

            Đăng nhận xét

            0 Nhận xét