你大概率已经在编程语言里见过条件表达式了,比如 if-else、switch-case 这些结构。在 SQL 里也有自己的条件工具:就是 CASE 表达式。它可以让你直接在查询里做决策:如果条件成立,做这个,否则做另一个。
CASE 结构特别有用,尤其是你要处理可能有 NULL 的数据时。语法超级简单,像小抄一样,来看看:
CASE
WHEN 条件1 THEN 结果1
WHEN 条件2 THEN 结果2
...
ELSE 默认结果
END
是不是很顺?“如果这样,就做这个,否则做那个”。记住也很容易:"WHEN 就是如果,THEN 就是做啥,ELSE 就是啥都不符合时做啥"。
例子:商品分类
假设我们有个 products 表,里面有个 price 列,我们想根据价格把商品分组。
| 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 '未知'
WHEN price < 50 THEN '低价'
WHEN price BETWEEN 50 AND 100 THEN '标准'
ELSE '高端'
END AS price_category
FROM products;
这里发生了啥?
- 如果商品的
price没填(NULL),我们显示分类为'未知'。 - 如果价格小于 50,这个商品算低价
'低价'。 - 如果价格在 50 到 100 之间,就是
'标准'。 - 剩下的都是高端商品
'高端'。
结果如下:
| product_name | price | price_category |
|---|---|---|
| Magic Wand | 120 | 高端 |
| Potion Set | 45 | 低价 |
| Crystal Ball | 75 | 标准 |
| Enchanted Map | NULL | 未知 |
| Broomstick | 99 | 标准 |
| Spell Book | 180 | 高端 |
SQL 就像魔法师一样,读 products 表的每一行,给它们分门别类。
在 CASE WHEN 里处理 NULL
数据里经常会有缺失值(hello,NULL),得用别的东西替换。比如我们有个 users 表,有 email 列,想让没邮箱的用户显示 '未填写'。
| 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 '未填写'
ELSE email
END AS email_address
FROM users;
| user_id | name | email_address |
|---|---|---|
| 1 | Alex Lin | alex@example.com |
| 2 | Maria Chi | 未填写 |
| 3 | Anna Song | anna@magic.com |
| 4 | Otto Art | 未填写 |
| 5 | John Smith | john@wizard.org |
SQL 在这里就像萨满,给半空的行加点料。没有 email 就用 '未填写',有就直接显示。
数字条件表达式
有时候不只是替换,还要搞点新逻辑。比如我们有个 students 表,有 score(分数)和 name 列。
| name | score |
|---|---|
| Alex Lin | 95 |
| Maria Chi | 82 |
| Anna Song | 48 |
| Otto Art | NULL |
| John Smith | 67 |
| Zoe Black | 30 |
我们想根据分数给学生打标签:"优秀"、"及格"、"不及格"。
SELECT
name AS student_name,
score,
CASE
WHEN score IS NULL THEN '无分数'
WHEN score >= 90 THEN '优秀'
WHEN score >= 50 THEN '及格'
ELSE '不及格'
END AS performance_category
FROM students;
| student_name | score | performance_category |
|---|---|---|
| Alex Lin | 95 | 优秀 |
| Maria Chi | 82 | 及格 |
| Anna Song | 48 | 不及格 |
| Otto Art | NULL | 无分数 |
| John Smith | 67 | 及格 |
| Zoe Black | 30 | 不及格 |
SQL 很贴心地帮你分好类:90 分以上就是“优秀”,50 分以上“及格”,不够就“挂科”。没分数就“无分数”。
分组和 NULL 处理
处理数据分组也是 CASE WHEN 的强项。比如我们有个 orders 表,想统计每个状态的订单数,包括 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 '无状态'
ELSE status
END AS order_status,
COUNT(*)
FROM orders
GROUP BY
CASE
WHEN status IS NULL THEN '无状态'
ELSE status
END;
这个查询会把 NULL 状态变成 '无状态',然后统计每组订单数。结果如下:
| order_status | count |
|---|---|
| Completed | 3 |
| Pending | 2 |
| Shipped | 2 |
| 无状态 | 3 |
实用场景:"CASE WHEN" 的小魔术
例子 1:排序时处理 NULL
有时候你想让 NULL 的值在排序时排最前或最后。比如任务列表,优先级高的排前面,没优先级(NULL)的排最后。
| 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;
我们加了个“虚拟”列 priority_sort,让 NULL 的都排在最后,其他的按升序排。
| 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 |
例子 2:带 NULL 的计算
再比如我们要算订单总价,orders 表里 discount(折扣)有可能是 NULL,没折扣时就是 NULL。
| order_id | total_price | discount |
|---|---|---|
| 101 | 100 | 10 |
| 102 | 200 | NULL |
| 103 | 150 | 15 |
| 104 | 120 | NULL |
| 105 | 80 | 5 |
我们要把 NULL 换成 0,这样计算才不会出错。
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 |
这个 CASE 小魔术让 NULL 不会让你的数学崩掉。
比如 orderid = 101: discount = 10. finalprice = 100 - 10 = 90。orderid = 102: discount = NULL. CASE 返回 0. finalprice = 200 - 0 = 200。orderid = 103: discount = 15. finalprice = 150 - 15 = 135。orderid = 104: discount = NULL. CASE 返回 0. finalprice = 120 - 0 = 120。orderid = 105: discount = 5. finalprice = 80 - 5 = 75。
例子 3:显示用户状态
日常开发经常要显示用户状态(比如“活跃”或“等待中”),或者提示没数据。比如 users 表有 last_login(最后登录时间)。
| 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 '从未登录'
WHEN last_login < CURRENT_DATE - INTERVAL '30 days' THEN '不活跃'
ELSE '活跃'
END AS user_status
FROM users;
这个查询让后台系统更有灵魂:没登录过的叫“从未登录”,很久没登录的叫“不活跃”,其他的就是“活跃”。
| user_id | name | user_status |
|---|---|---|
| 1 | Alex Lin | 不活跃 |
| 2 | Maria Chi | 不活跃 |
| 3 | Anna Song | 从未登录 |
| 4 | Otto Art | 不活跃 |
| 5 | Liam Park | 活跃 |
常见错误和如何避免
漏写 ELSE: 如果你没写 ELSE,SQL 在所有条件都不满足时会直接返回 NULL。这不一定是你想要的。所以最好总是写上 ELSE,哪怕你觉得所有情况都考虑到了。
CASE
WHEN condition THEN result
-- ELSE '默认值' -- 别忘了!
END
复杂条件不加括号: 如果你有多个 AND、OR 或 NOT,一定要用括号。不然 SQL 会“想歪”。
CASE
WHEN (column1 IS NOT NULL AND column2 > 5) THEN '有效'
ELSE '无效'
END
处理 NULL: 记住 NULL 永远不等于(=)任何东西。比如:
CASE
WHEN column = NULL THEN '错啦!' -- 错误!
WHEN column IS NULL THEN '对啦!' -- 这样才对。
END
GO TO FULL VERSION