条件表达式
本教程共 50 篇 · 第 35 篇 · 更新于 2026-07-31 · 约 11 分钟阅读
35. 条件表达式
本节目标:学会在查询里写分支判断,掌握 CASE WHEN、COALESCE、NULLIF、GREATEST / LEAST 四种条件表达式,能优雅地处理 NULL、取极值、防除零,并知道它们和其他数据库的兼容差异。
SQL 里也有「如果……就……否则……」的逻辑,这靠条件表达式实现。它们是表达式,所以可以放在 SELECT、WHERE、ORDER BY、GROUP BY 等任何能写表达式的地方。和「存储过程里的 IF」不同,条件表达式是在一条普通查询里就地计算的,不控制流程。
继续用 users 和 orders 两张表演示(结构见前面章节)。先把它们建出来:
CREATE TABLE users (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age INT,
created_at TIMESTAMP DEFAULT now()
);
CREATE TABLE orders (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
user_id INT,
amount NUMERIC(10,2),
status TEXT,
created_at TIMESTAMP DEFAULT now()
);
CASE WHEN:SQL 里的 if/else
CASE 有两种写法。第一种是「搜索型(searched)」,挨个判断条件:
SELECT username, age,
CASE
WHEN age < 25 THEN '青年'
WHEN age < 35 THEN '中年'
ELSE '资深'
END AS 年龄段
FROM users;
从上往下判断,命中第一个为真的 WHEN 就返回对应 THEN,后面的不再看;都不中走 ELSE;省略 ELSE 则返回 NULL。这个「短路」特性很重要——把最具体的条件放前面,避免被更宽的条件先截走。
CASE 还能和聚合函数配合做「分类统计」。比如统计各年龄段的用户数:
SELECT
SUM(CASE WHEN age < 25 THEN 1 ELSE 0 END) AS 青年数,
SUM(CASE WHEN age >= 25 AND age < 35 THEN 1 ELSE 0 END) AS 中年数,
SUM(CASE WHEN age >= 35 THEN 1 ELSE 0 END) AS 资深数
FROM users;
这种「在聚合里套 CASE」的手法,叫「条件聚合(conditional aggregation)」,做交叉报表特别好用。
第二种是「简单型(simple)」,先给一个表达式再逐个比对值:
SELECT username,
CASE status
WHEN 'paid' THEN '已支付'
WHEN 'pending' THEN '待支付'
ELSE '其他'
END AS 状态中文
FROM orders;
简单型只能做相等比较;要做范围或复杂条件,用搜索型。
Tip
CASE只算「必要的」分支,所以可以用它避开除零错误:CASE WHEN x <> 0 THEN y/x ELSE NULL END,x 为 0 时不会真的去算除法。同理可避免对无效数据做计算。
COALESCE:取第一个非空值
COALESCE(值1, 值2, ...) 从左往右返回第一个不是 NULL 的值;全是 NULL 才返回 NULL。它常用来给缺失值填默认值。
-- 邮箱为空时显示「未填写」
SELECT username,
COALESCE(email, '未填写') AS 邮箱显示
FROM users;
COALESCE 接受任意多个参数,类型要能互相兼容。它本质是「依次判断、取到第一个非空」,所以 COALESCE(a, b, c) 等价于嵌套的 CASE WHEN a IS NOT NULL THEN a WHEN b IS NOT NULL THEN b ELSE c END。它和 Oracle 里的 NVL、MySQL 里的 IFNULL 作用类似,但 COALESCE 是标准写法、参数更多,可移植性更好。
NULLIF:相等则返回 NULL
NULLIF(值1, 值2) 的逻辑是:如果两值相等就返回 NULL,否则返回 值1。它像 COALESCE 的反向操作,也常被用来防除零。
-- 用一行测试数据演示:quantity 为 0 时,除式结果变成 NULL 而不会报错
SELECT amount / NULLIF(quantity, 0) AS 单价
FROM (VALUES (100.00, 2), (50.00, 0)) AS t(amount, quantity);
当 quantity 为 0 时,NULLIF(quantity, 0) 变 NULL,整个表达式结果也是 NULL,而不会报「除以零」的错误。配合 COALESCE 还能给个兜底:COALESCE(amount / NULLIF(quantity, 0), 0) 让除零时显示 0 而不是 NULL。
GREATEST 与 LEAST:取最大 / 最小
GREATEST(值1, 值2, ...) 返回列表里最大的,LEAST(...) 返回最小的。参数个数不限。
-- 假设想给每个订单设一个「保底金额」:不少于 10
SELECT id, amount,
GREATEST(amount, 10) AS 实际计额
FROM orders;
一个实用场景是「封顶/保底」:工资不低于当地最低标准 GREATEST(salary, 2280),或奖金不超过上限 LEAST(bonus, 10000)。
Warning这两个函数会忽略参数里的
NULL。只有全部参数都是NULL时才返回NULL。这点和部分其他数据库(按 SQL 标准严格实现时,任一为 NULL 整个结果就返回 NULL)不同,从别的库迁移时要留心。如果你希望「任一为 NULL 就返回 NULL」,得自己加判断,比如CASE WHEN x IS NULL OR y IS NULL THEN NULL ELSE GREATEST(x, y) END。
综合运用
条件表达式经常组合使用。比如「把空邮箱替换成占位符,再按用户名和邮箱取较长者展示」:
SELECT username,
GREATEST(
LENGTH(username),
LENGTH(COALESCE(email, ''))
) AS 最长字段长度
FROM users;
再举一个报表里常见的:把订单状态翻译成中文,再把空邮箱补成「未填写」,最后按状态排序——条件表达式能把这些杂活都塞进一条 SELECT 里,省得在应用代码里再判断一遍。
SELECT
username,
COALESCE(email, '未填写') AS 邮箱,
CASE status
WHEN 'paid' THEN '已支付'
WHEN 'pending' THEN '待支付'
ELSE '其他'
END AS 状态
FROM users u
JOIN orders o ON o.user_id = u.id
ORDER BY 状态;
常见错误
- 搜索型
CASE把范围宽的条件写在前面,导致后面的窄条件永远命中不了(顺序很重要)。 - 以为
GREATEST/LEAST碰到NULL会返回另一个值——实际上它会忽略NULL。 - 用
=去判断NULL,结果始终 unknown;判断空值要用IS NULL,或用COALESCE先转成可比较的值。
CASE 在 ORDER BY 里自定义排序
CASE 还能放进 ORDER BY,做「不按字母、按业务优先级」的排序。比如让「待支付」订单排最前:
SELECT id, status, amount
FROM orders
ORDER BY
CASE status
WHEN 'pending' THEN 1
WHEN 'paid' THEN 2
ELSE 3
END,
amount DESC;
这样不用改数据,就能让待处理事项永远置顶。
用条件表达式做行转列(透视)
配合 GROUP BY,CASE 能把「多行分类」压成「一行多列」。比如按状态统计订单金额:
SELECT
user_id,
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS 已付,
SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) AS 待付
FROM orders
WHERE user_id IS NOT NULL
GROUP BY user_id;
本来每个用户有多行订单,结果被透视成一行的「已付 / 待付」两列。报表里这种写法极常见。
COALESCE 与 NULLIF 组合
两者常搭档:先用 NULLIF 把「无效值」变 NULL,再用 COALESCE 兜底成默认值。
-- 若 amount 为 0 视为缺失,统一显示 0;否则显示原值
SELECT COALESCE(NULLIF(amount, 0), 0) FROM orders;
条件表达式可以嵌套
CASE 里能再套 CASE,COALESCE 的参数也能是另一个条件表达式。比如先给邮箱兜底,再按年龄段细分:
SELECT username,
CASE
WHEN age IS NULL THEN '年龄未知'
ELSE CASE
WHEN age < 25 THEN '青年'
ELSE '成年'
END
END AS 分组
FROM users;
嵌套层数别太多,否则可读性会下降。真要套多层,我倾向于拆成多个 CTE 步骤,一步步算,比一层层 CASE 好读。另外要记住:所有条件表达式在 NULL 上都要小心——COALESCE 正是为 NULL 而生,但 GREATEST/LEAST 会忽略 NULL,两者思路不同,别混用。
用条件表达式暴露脏数据
条件表达式还是「数据体检」的好工具。比如把异常金额归并成标记,能在报表里快速暴露问题:
SELECT id, amount,
CASE WHEN amount < 0 THEN '异常' ELSE '正常' END AS 校验
FROM orders;
条件表达式本身不修改数据,只是计算列,所以你想怎么套都安全,不会影响原表。它和 WHERE 搭配还能直接筛出异常行,是入库前做轻量校验的常用手段。
小结
条件表达式让 SQL 也能做分支和兜底:CASE WHEN 写完整 if/else,是万能的(也支持简单型做相等比对);COALESCE 专填 NULL 默认值;NULLIF 相等变 NULL、常用来防除零;GREATEST/LEAST 取极值但要注意它们忽略 NULL。它们都只是表达式,随手就能嵌进查询里,把数据清洗的活直接做在数据库侧。