首页 / PostgreSQL 入门教程 / 条件表达式

PostgreSQL 入门教程

条件表达式

本教程共 50 篇 · 第 35 篇 · 更新于 2026-07-31 · 约 11 分钟阅读

PostgreSQLPostgreSQL 入门教程条件表达式CASECOALESCENULLIF

35. 条件表达式

本节目标:学会在查询里写分支判断,掌握 CASE WHEN、COALESCE、NULLIF、GREATEST / LEAST 四种条件表达式,能优雅地处理 NULL、取极值、防除零,并知道它们和其他数据库的兼容差异。

SQL 里也有「如果……就……否则……」的逻辑,这靠条件表达式实现。它们是表达式,所以可以放在 SELECTWHEREORDER BYGROUP BY 等任何能写表达式的地方。和「存储过程里的 IF」不同,条件表达式是在一条普通查询里就地计算的,不控制流程。

继续用 usersorders 两张表演示(结构见前面章节)。先把它们建出来:

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 状态;

常见错误

  1. 搜索型 CASE 把范围宽的条件写在前面,导致后面的窄条件永远命中不了(顺序很重要)。
  2. 以为 GREATEST/LEAST 碰到 NULL 会返回另一个值——实际上它会忽略 NULL
  3. = 去判断 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 BYCASE 能把「多行分类」压成「一行多列」。比如按状态统计订单金额:

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 里能再套 CASECOALESCE 的参数也能是另一个条件表达式。比如先给邮箱兜底,再按年龄段细分:

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。它们都只是表达式,随手就能嵌进查询里,把数据清洗的活直接做在数据库侧。