MySQL CASE WHEN实战:从基础语法到复杂业务逻辑的进阶指南
1. CASE WHEN基础语法从零开始掌握条件判断刚接触MySQL时我总在想如何像编程语言那样在SQL里做条件判断。直到发现了CASE WHEN这个神器它简直就是SQL里的if-else语句。先来看最基础的两种写法第一种是简单模式适合对固定值进行判断SELECT product_name, CASE status WHEN in_stock THEN 有货 WHEN out_of_stock THEN 缺货 ELSE 未知状态 END AS stock_status FROM products;这种写法清晰直观把字段值直接与WHEN后面的值比较。我刚开始做电商项目时就用这种方式把后台的英文状态码转成了前端展示的中文。第二种是搜索模式灵活性更高SELECT order_id, CASE WHEN total_amount 1000 THEN 大客户订单 WHEN total_amount 500 AND create_time 2023-01-01 THEN 重点订单 WHEN payment_method alipay THEN 支付宝订单 ELSE 普通订单 END AS order_type FROM orders;这种模式下每个WHEN后面可以写任意复杂的条件表达式。记得去年双十一大促时我们就是用这种写法实现了动态订单分类根据不同金额和时间段给订单打标签。2. 嵌套CASE WHEN处理多层业务逻辑当业务逻辑变得复杂时单层CASE WHEN可能不够用。这时就需要嵌套使用就像编程中的if-else嵌套一样。举个实际例子我们电商平台的会员折扣系统SELECT user_id, CASE WHEN vip_level gold THEN CASE WHEN order_amount 1000 THEN order_amount * 0.7 -- 黄金会员满1000打7折 WHEN order_amount 500 THEN order_amount * 0.8 -- 满500打8折 ELSE order_amount * 0.9 -- 默认9折 END WHEN vip_level silver THEN CASE WHEN order_amount 800 THEN order_amount * 0.8 WHEN order_amount 300 THEN order_amount * 0.85 ELSE order_amount * 0.95 END ELSE order_amount -- 普通会员无折扣 END AS final_amount FROM user_orders;这种嵌套结构虽然强大但要注意两点层级不宜过深最好不超过3层否则SQL会变得难以维护每个END要对应正确的CASE建议用缩进保持清晰我在实际项目中踩过的坑是有一次嵌套了5层CASE WHEN后来需求变更时要调整中间层的逻辑花了半天时间才理清楚层次关系。3. 聚合函数中的CASE WHEN高级统计技巧CASE WHEN与聚合函数结合能实现非常灵活的数据统计。这是数据分析中最常用的技巧之一。3.1 条件计数统计不同状态的订单数量SELECT COUNT(*) AS total_orders, SUM(CASE WHEN status paid THEN 1 ELSE 0 END) AS paid_count, SUM(CASE WHEN status unpaid THEN 1 ELSE 0 END) AS unpaid_count, SUM(CASE WHEN status refunded THEN 1 ELSE 0 END) AS refunded_count FROM orders;这种写法比分别用多个COUNT查询高效得多因为只需要扫描一次表。3.2 动态分组统计做报表时经常需要按条件分组统计SELECT department, SUM(CASE WHEN gender M THEN 1 ELSE 0 END) AS male_count, SUM(CASE WHEN gender F THEN 1 ELSE 0 END) AS female_count, AVG(CASE WHEN education master THEN salary ELSE NULL END) AS master_avg_salary, AVG(CASE WHEN education bachelor THEN salary ELSE NULL END) AS bachelor_avg_salary FROM employees GROUP BY department;这里有个技巧用NULL排除不符合条件的记录这样AVG计算时就会自动忽略这些值。4. 性能优化与实战技巧4.1 索引使用注意事项CASE WHEN条件中的字段如果使用了函数或运算可能会导致索引失效-- 不好的写法索引可能失效 SELECT * FROM products WHERE CASE WHEN discount_price 0 THEN discount_price ELSE original_price END 100; -- 优化后的写法 SELECT * FROM products WHERE (discount_price 0 AND discount_price 100) OR (discount_price 0 AND original_price 100);4.2 NULL值处理处理NULL值时需要特别注意-- 错误的NULL判断 SELECT CASE address WHEN NULL THEN 未知地址 ELSE address END FROM users; -- 正确的写法 SELECT CASE WHEN address IS NULL THEN 未知地址 ELSE address END FROM users;4.3 类型一致性确保所有THEN返回的数据类型兼容否则可能出现意外结果-- 混合类型可能导致问题 SELECT CASE WHEN score 90 THEN 优秀 WHEN score 80 THEN 85 -- 这里返回数字 ELSE 及格 END AS evaluation FROM students; -- 统一类型更安全 SELECT CASE WHEN score 90 THEN 优秀 WHEN score 80 THEN 良好 ELSE 及格 END AS evaluation FROM students;5. 复杂业务场景实战5.1 动态价格计算电商平台经常需要根据多种因素计算最终价格SELECT product_id, original_price, CASE WHEN is_pre_sale 1 THEN original_price * 0.8 -- 预售8折 WHEN member_level gold AND stock 100 THEN original_price * 0.7 -- 黄金会员且库存充足7折 WHEN EXISTS ( SELECT 1 FROM flash_sales WHERE flash_sales.product_id products.product_id ) THEN (SELECT sale_price FROM flash_sales WHERE flash_sales.product_id products.product_id) ELSE original_price END AS final_price FROM products;5.2 用户分层运营根据用户行为数据进行分层SELECT user_id, last_login_date, order_count, CASE WHEN last_login_date DATE_SUB(NOW(), INTERVAL 30 DAY) AND order_count 5 THEN 高价值活跃用户 WHEN last_login_date DATE_SUB(NOW(), INTERVAL 30 DAY) AND order_count 5 THEN 新活跃用户 WHEN last_login_date DATE_SUB(NOW(), INTERVAL 30 DAY) AND order_count 10 THEN 高价值沉默用户 WHEN last_login_date DATE_SUB(NOW(), INTERVAL 90 DAY) THEN 流失用户 ELSE 普通用户 END AS user_segment FROM user_behavior_stats;5.3 多维度报表统计销售报表常用这种行转列的方式SELECT salesperson, SUM(CASE WHEN quarter Q1 THEN amount ELSE 0 END) AS Q1_sales, SUM(CASE WHEN quarter Q2 THEN amount ELSE 0 END) AS Q2_sales, SUM(CASE WHEN quarter Q3 THEN amount ELSE 0 END) AS Q3_sales, SUM(CASE WHEN quarter Q4 THEN amount ELSE 0 END) AS Q4_sales, SUM(amount) AS annual_sales, ROUND(SUM(amount) / NULLIF(SUM(CASE WHEN quarter IN (Q1,Q2,Q3,Q4) THEN 1 ELSE 0 END), 0), 2) AS avg_per_quarter FROM sales_data GROUP BY salesperson;