Mysql 8.4 参考文档:
https://dev.mysql.com/doc/refman/8.4/en/date-and-time-functions.html
基础用法
-- 简单 CASE 表达式
CASE column
WHEN value1 THEN result1
WHEN value2 THEN result2
ELSE default_result
END
-- 搜索 CASE 表达式
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
ELSE default_result
END
** 1、自定义排序**
SQL的ORDER BY 默认按字母排序,数字大小或日期先后排序,但业务需求往往不按常理出牌:
1. 想要订单状态按【待支付--已支付--已发货--已完成】排序
2. 想要用户等级【砖石--黄金--白银--青铜】排序
3. 想要星期从【周一】开始排,而不是【周五】
这就需要自定义排序--你说了算
2、核心语法:CASE WHEN + ORDER BY
order by case 字段名
when '值1' then 1
when '值2' then 2
when '值3' then 3
else 99
end
原理:把业务含义转换为数字,数据库按数字排序,数字小的排在前面。
3、应用场景
场景1:订单状态排序
业务需求:用户想要订单列表,按【待支付--已支付--已发货--已完成--已取消】的顺序展示
select order_id,custoer_name,status from orders
order by case status
when '待支付' then 1
when '已支付' then 2
when '已发货' then 3
when '已完成' then 4
when '已取消' then 5
else 99
end;
场景2:星期排序(从周一开始)
select sale_date,WEEKDAY(sale_date) as weekday_num,
sum(amount) as total_sales
from sales group by sale_date
order by case DAYOFWEEK(sale_date)
when 2 then 1 --周一
when 3 then 2 --周二
when 4 then 3 --周三
when 5 then 4 --周四
when 6 then 5 --周五
when 7 then 6 --周六
when 1 then 7 --周日
end;
4、多级自定义排序
select order_id,status,create_time from orders order by case status when '待支付' then 1
when '已支付' then 2
when '已发货' then 3
else 99
end, create_time desc; --同状态按照创建时间排序
效果:所有【待支付】排在最前面(按时间倒序),然后是已支付,已发货
五、记得处理else
不在CASE里的值会归到ELSE,如果不写ELSE,这些值会变成NULL,NULL在排序中会默认排最前或最后(取决于数据库),可能导致意外
数字不一定连续
THEN 1、THEN 100、THEN 500 完全可以,只要大小关系正确
order by CASE level
when '砖石' then 10
when '黄金' then 20
when '白银' then 30
end
-- 10<20<30,排序正确
可以组合多个CASE
order by
CASE type when 'VIP' then 1 else 2 end,
case region when '华北' then 1 else 2 end
数组分组统计(行转列)
-- 按月份统计不同状态的订单数量
SELECT
YEAR(order_date) AS year,
MONTH(order_date) AS month,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) AS completed_count,
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) AS pending_count,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_count,
COUNT(*) AS total_count
FROM orders
GROUP BY YEAR(order_date), MONTH(order_date);
条件聚合计算
-- 根据不同条件计算不同的聚合值
SELECT
department_id,
AVG(CASE WHEN gender = 'M' THEN salary END) AS avg_male_salary,
AVG(CASE WHEN gender = 'F' THEN salary END) AS avg_female_salary,
MAX(CASE WHEN job_level = 'senior' THEN salary END) AS max_senior_salary,
MIN(CASE WHEN job_level = 'junior' THEN salary END) AS min_junior_salary
FROM employees
GROUP BY department_id;
自定义排序
-- 根据不同条件计算不同的聚合值
SELECT
department_id,
AVG(CASE WHEN gender = 'M' THEN salary END) AS avg_male_salary,
AVG(CASE WHEN gender = 'F' THEN salary END) AS avg_female_salary,
MAX(CASE WHEN job_level = 'senior' THEN salary END) AS max_senior_salary,
MIN(CASE WHEN job_level = 'junior' THEN salary END) AS min_junior_salary
FROM employees
GROUP BY department_id;
数据透视表
-- 将行数据转换为列
SELECT
student_name,
MAX(CASE WHEN subject = 'math' THEN score END) AS math_score,
MAX(CASE WHEN subject = 'english' THEN score END) AS english_score,
MAX(CASE WHEN subject = 'science' THEN score END) AS science_score
FROM student_scores
GROUP BY student_name;
空值处理
-- 处理 NULL 值并提供默认值
SELECT
product_name,
CASE
WHEN description IS NULL OR description = '' THEN 'No description available'
ELSE description
END AS display_description,
COALESCE(
CASE
WHEN discount_price > 0 THEN discount_price
ELSE original_price
END,
original_price
) AS final_price
FROM products;
update语句中的case when
-- 批量更新不同条件的数据
UPDATE employees
SET
bonus = CASE
WHEN performance_score >= 90 THEN salary * 0.2
WHEN performance_score >= 80 THEN salary * 0.15
WHEN performance_score >= 70 THEN salary * 0.1
ELSE salary * 0.05
END,
level = CASE
WHEN years_of_service >= 10 THEN 'Senior'
WHEN years_of_service >= 5 THEN 'Mid'
ELSE 'Junior'
END
WHERE department_id = 1;
根据参数动态排序
-- 根据参数动态排序
SELECT * FROM products
ORDER BY
CASE
WHEN @sort_by = 'price_asc' THEN price
END ASC,
CASE
WHEN @sort_by = 'price_desc' THEN price
END DESC,
CASE
WHEN @sort_by = 'name' THEN product_name
END ASC;
having子句的使用
-- 在 HAVING 中使用 CASE WHEN 进行条件过滤
SELECT
department_id,
COUNT(*) AS emp_count,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id
HAVING
SUM(CASE WHEN salary > 5000 THEN 1 ELSE 0 END) > 5
AND AVG(CASE WHEN gender = 'M' THEN salary END) > 6000;
join条件中使用
-- 在 JOIN 条件中使用 CASE WHEN
SELECT
o.order_id,
c.customer_name,
CASE
WHEN o.total_amount > 1000 THEN 'Premium'
ELSE 'Standard'
END AS customer_tier
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
LEFT JOIN vip_members v ON
c.customer_id = v.customer_id
AND CASE
WHEN v.expiry_date > CURDATE() THEN 1
ELSE 0
END = 1;
嵌套case when
-- 嵌套使用 CASE WHEN
SELECT
employee_name,
CASE
WHEN department = 'Sales' THEN
CASE
WHEN sales_volume > 100000 THEN 'Top Sales'
WHEN sales_volume > 50000 THEN 'Good Sales'
ELSE 'Normal Sales'
END
WHEN department = 'Tech' THEN
CASE
WHEN projects_completed > 10 THEN 'Senior Dev'
WHEN projects_completed > 5 THEN 'Mid Dev'
ELSE 'Junior Dev'
END
ELSE 'Other'
END AS employee_category
FROM employees;
日期时间处理
-- 根据日期时间进行分类
SELECT
order_id,
order_date,
CASE
WHEN HOUR(order_time) BETWEEN 6 AND 11 THEN 'Morning'
WHEN HOUR(order_time) BETWEEN 12 AND 17 THEN 'Afternoon'
WHEN HOUR(order_time) BETWEEN 18 AND 23 THEN 'Evening'
ELSE 'Night'
END AS time_period,
CASE DAYOFWEEK(order_date)
WHEN 1 THEN 'Sunday'
WHEN 7 THEN 'Saturday'
ELSE 'Weekday'
END AS day_type
FROM orders;
窗口函数使用
-- 与窗口函数结合使用
SELECT
employee_name,
department_id,
salary,
CASE
WHEN RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) = 1 THEN 'Highest'
WHEN RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) <= 3 THEN 'Top 3'
ELSE 'Normal'
END AS salary_rank_category
FROM employees;
INSERT INTO SELECT 中使用
-- 在插入数据时使用 CASE WHEN 转换
INSERT INTO employee_summary (emp_id, emp_name, salary_level, age_group)
SELECT
id,
name,
CASE
WHEN salary >= 10000 THEN 'High'
WHEN salary >= 5000 THEN 'Medium'
ELSE 'Low'
END,
CASE
WHEN age < 30 THEN 'Young'
WHEN age < 50 THEN 'Middle'
ELSE 'Senior'
END
FROM employees;
计算字段和指标
-- 计算复杂的业务指标
SELECT
DATE_FORMAT(order_date, '%Y-%m') AS month,
COUNT(*) AS total_orders,
SUM(total_amount) AS total_revenue,
SUM(CASE WHEN status = 'completed' THEN total_amount ELSE 0 END) AS completed_revenue,
SUM(CASE WHEN status = 'refunded' THEN total_amount ELSE 0 END) AS refunded_amount,
ROUND(
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) * 100.0 / COUNT(*),
2
) AS completion_rate
FROM orders
GROUP BY DATE_FORMAT(order_date, '%Y-%m');
案例
-- 电商订单分析报表
SELECT
DATE_FORMAT(o.order_date, '%Y-%m-%d') AS order_day,
COUNT(DISTINCT o.customer_id) AS unique_customers,
COUNT(o.order_id) AS total_orders,
SUM(o.total_amount) AS gmv,
SUM(CASE WHEN o.status = 'paid' THEN o.total_amount ELSE 0 END) AS paid_gmv,
SUM(CASE WHEN o.status = 'refunded' THEN o.total_amount ELSE 0 END) AS refund_gmv,
ROUND(AVG(o.total_amount), 2) AS avg_order_value,
SUM(CASE WHEN p.category = 'electronics' THEN 1 ELSE 0 END) AS electronics_orders,
SUM(CASE WHEN p.category = 'clothing' THEN 1 ELSE 0 END) AS clothing_orders
FROM orders o
LEFT JOIN order_items oi ON o.order_id = oi.order_id
LEFT JOIN products p ON oi.product_id = p.product_id
GROUP BY DATE_FORMAT(o.order_date, '%Y-%m-%d')
ORDER BY order_day DESC;