COALESCE / NULLIF
COALESCE 与 NULLIF 是 PostgreSQL 中仅有的两个直接处理 NULL 语义的条件函数:前者从参数列表里挑出第一个非 NULL 的值,后者把一个"等于特定值"的输入转换成 NULL。它们不是普通的 C 函数,而是由解析器识别、由优化器直接处理的条件表达式节点——这个身份决定了它们的类型推导规则、短路行为与索引可用性都与众不同。本节从函数视角讲解它们的定义、类型规则,以及在 DDL、默认值、UPSERT 回填与聚合中的实际用法。
与「COALESCE / NULLIF / GREATEST / LEAST」运算符章节的分工
COALESCE / NULLIF / GREATEST / LEAST 一章从表达式写法出发,重点讲"怎么写、什么时候用";本节从函数视角出发,重点讲参数个数与类型推导规则、短路求值的边界、以及在 DEFAULT / CHECK / ON CONFLICT / 表达式索引中的落地方式。两节互补,避免只知语法不知边界。
前置知识
阅读本节前,建议先了解:ORDER BY 在聚合中
基础概念
| 概念 | 术语 | 说明 |
|---|---|---|
| 空值兜底 | COALESCE | 返回参数列表中第一个非 NULL 的参数 |
| 相等转空 | NULLIF | 两个参数相等时返回 NULL,否则返回第一个参数 |
| 条件表达式节点 | Conditional Expression | COALESCE / NULLIF 在解析树中是特殊节点,不是 pg_proc 里的普通函数 |
| 类型归约 | Type Unification | 所有参数先被推导到一个共同类型,返回值是该类型 |
| 短路求值 | Short-Circuit | 一旦结果确定,后续参数不再求值 |
| 未知类型字面量 | Unknown Literal | 'abc' 这类未加类型标注的字符串常量,参与类型归约 |
| 值域默认化 | Defaulting | 用 COALESCE 把 NULL 变成业务默认值,避免下游判空 |
| 无效值归一 | Sentinel-to-NULL | 用 NULLIF 把 0、空串等哨兵值归一为 NULL |
两者的签名(pg_proc 中并不存在 coalesce 这一行,下面是语义签名):
| 表达式 | 语义签名 | 返回类型 |
|---|---|---|
COALESCE | coalesce(v1, v2, ...) | 所有参数的公共类型 |
NULLIF | nullif(v1, v2) | 第一个参数 v1 的类型 |
最小示例:
SELECT
coalesce(NULL, NULL, '兜底') AS pick_third, -- 兜底
coalesce(NULL, 'B', 'C') AS pick_second, -- B
nullif(10, 10) AS equal_to_null,-- NULL
nullif(10, 3) AS not_equal; -- 10 pick_third | pick_second | equal_to_null | not_equal
------------+-------------+---------------+-----------
兜底 | B | | 10
(1 row)语法与用法
COALESCE ( 值1 [, 值2, ...] )
NULLIF ( 值1, 值2 )两条必须记住的硬规则:
COALESCE的参数个数不限,但 SQL 标准要求至少两个(PostgreSQL 实现上允许更少,但单参数没有任何意义);NULLIF恰好两个参数,需要排除多个无效值时只能嵌套NULLIF。
-- 标准形态
SELECT
coalesce(a, b, c, d, '默认') AS first_non_null,
nullif(status, 'unknown') AS normalized_status
FROM t;最小可运行示例,覆盖两个函数的返回值边界:
SELECT
coalesce(NULL) AS only_null, -- NULL(全为 NULL 则返回 NULL)
coalesce(NULL, 0) AS zero_is_value, -- 0(0 不是 NULL)
nullif(NULL, 'x') AS null_v1, -- NULL(第一个参数原样返回)
nullif('x', NULL) AS null_v2; -- x(NULL 与任何值都不相等) only_null | zero_is_value | null_v1 | null_v2
-----------+---------------+---------+---------
| 0 | | x
(1 row)详细说明
COALESCE 的求值模型
COALESCE 从左到右扫描参数列表,返回第一个"不是 SQL NULL"的值;如果全部为 NULL,结果是 NULL。它不关心值的内容,只判断"是否为 NULL":
SELECT
coalesce(''::text, '兜底') AS empty_string_is_value, -- 空串(不是 NULL!)
coalesce(0, 100) AS zero_is_value, -- 0
coalesce(false, true) AS false_is_value, -- f
coalesce(NULL, NULL, NULL) AS all_null; -- NULL empty_string_is_value | zero_is_value | false_is_value | all_null
-----------------------+---------------+----------------+----------
| 0 | f |
(1 row)这条性质决定了 COALESCE 不能替代业务条件判断:它只会替换 NULL,不会替换空串、0、false 这些"业务上的无效值"。要处理空串必须先 NULLIF 归一,这也正是 coalesce(nullif(trim(x), ''), '默认') 这个固定组合存在的原因。
类型推导规则
这是 COALESCE 最容易出错的部分。规则可以概括为三步:
1. 收集所有参数的类型;
2. 若存在「unknown」类型(无标注的字符串字面量),暂不作为候选;
3. 在已知类型中选出公共类型(同类型类别内按类别偏好),
把其余参数与 unknown 字面量统一转换到该类型;
若无法归约到同一类型 → 报错。| 参数组合 | 推导结果 | 说明 |
|---|---|---|
coalesce(NULL, 1, 2.5) | numeric | int4 与 numeric 归约到 numeric |
coalesce(1, 2) | integer | 同类型,保持 integer |
coalesce(1::smallint, 2) | integer | 归约到更宽的 integer |
coalesce(NULL, 'abc') | text | 唯一候选是 unknown,落到 text |
coalesce(1, 'abc') | 报错 | 'abc' 被解析为 integer 时失败 |
coalesce(1, true) | 报错 | 跨类型类别,无法归约 |
验证跨类型归约确实发生:
SELECT
coalesce(NULL, 1, 2.5) AS value,
pg_typeof(coalesce(NULL, 1, 2.5)) AS value_type,
pg_typeof(coalesce(1, 2)) AS int_type,
pg_typeof(coalesce(NULL, 'abc')) AS text_type; value | value_type | int_type | text_type
-------+------------+----------+-----------
1 | numeric | integer | text
(1 row)注意结果值显示为 1 而不是 1.0,但它的类型已经是 numeric——这正是"先归约类型、再取值"的证据。
跨类型类别会直接报错,且错误信息明确指出了冲突的两侧:
-- 错误!integer 与 boolean 属于不同类别,无法归约
SELECT coalesce(1, true);ERROR: COALESCE types integer and boolean cannot be matched
LINE 1: SELECT coalesce(1, true);-- 错误!'abc' 是 unknown 字面量,被归约到 integer 后无法转换
SELECT coalesce(1, 'abc');ERROR: invalid input syntax for type integer: "abc"
LINE 1: SELECT coalesce(1, 'abc');规避办法是显式转型,而不是靠隐式规则去猜:
-- 正确:显式把冲突一侧转型,或把字面量标注为目标类型
SELECT coalesce(1::text, 'abc') AS to_text, -- 1
coalesce(1, '2'::integer) AS to_int; -- 1短路求值
COALESCE 只求值"确定结果所必需"的参数——一旦某个参数非 NULL,它右边的参数完全不会被计算。这与 CASE 的行为一致,且是有文档保证的语义,可以放心依赖:
-- 安全:denominator 非 0 时,右侧的除法根本不会执行
SELECT coalesce(nullif(20, 0), 20 / 0) AS safe; -- 20,不会报 division by zero safe
------
20
(1 row)反过来,这个特性也能用来规避昂贵计算或易错表达式:
-- 若 cache_value 已有值,昂贵的子查询不会执行
SELECT coalesce(
cache_value,
(SELECT max(score) FROM huge_table) -- 只在 cache_value 为 NULL 时求值
) AS score
FROM score_cache
WHERE id = 1;短路 ≠ 不解析
短路只发生在执行期。参数的语法与类型仍会全部参与解析与类型推导——coalesce(1, true) 依然报类型错误,coalesce(1, 1/0) 也不会因为"根本没用到"而豁免类型检查。
NULLIF 的值语义与返回类型
NULLIF(v1, v2) 用 = 比较两个参数:相等返回 NULL,不等返回 v1。两个必须记住的性质:
- 返回类型是
v1的类型,不会因为比较而改变:sqlSELECT pg_typeof(nullif(1, 2)) AS t1, -- integer pg_typeof(nullif(1.5, 2)) AS t2, -- numeric pg_typeof(nullif('a'::text, 'b')) AS t3; -- text - 两个参数必须可比较,且类型要能归约:
-- 错误!无法归约到同一类型
SELECT nullif(1, true);ERROR: NULLIF types integer and boolean cannot be matched-- 错误!'x' 被归约为 integer 后转换失败
SELECT nullif(1, 'x');ERROR: invalid input syntax for type integer: "x"还有一个容易被忽略的语义细节:NULL = NULL 的结果是 NULL,而不是 true,所以当 v1 为 NULL 时 NULLIF 原样返回 NULL(结果相同);但当 v2 为 NULL 时永远不会相等,v1 原样返回:
SELECT
nullif(NULL, 1) AS v1_is_null, -- NULL
nullif('a', NULL) AS v2_is_null; -- a v1_is_null | v2_is_null
------------+------------
| a
(1 row)NULLIF 防除零的标准范式
a / NULLIF(b, 0) 是 SQL 世界里"防止 division by zero"的固定写法:当 b 为 0 时,NULLIF(b, 0) 返回 NULL,NULL 参与任何算术运算仍得 NULL,因此不会触发除零错误,只是结果变成 NULL。
-- 危险!b 为 0 时直接报错
SELECT 100 / 0;ERROR: division by zero-- 安全:分母为 0 时结果整体为 NULL,由调用方决定如何兜底
SELECT 100 / NULLIF(0, 0) AS result; -- NULL实际报表中,通常还要再用 COALESCE 把 NULL 兜成 0,形成 coalesce(a / NULLIF(b, 0), 0) 两级写法:
SELECT
campaign,
clicks,
orders,
coalesce(round(orders::numeric / NULLIF(clicks, 0), 4), 0) AS conversion_rate
FROM campaigns
ORDER BY id;| 写法 | b = 0 时 | b IS NULL 时 | 结论 |
|---|---|---|---|
a / b | 报错 | NULL | 不可用 |
a / coalesce(b, 1) | 仍报错,且结果错 | NULL | 错误用法 |
a / NULLIF(b, 0) | NULL | NULL | 正确 |
coalesce(a / NULLIF(b, 0), 0) | 0 | 0 | 最常用于报表 |
COALESCE 不能防除零
coalesce(b, 0) 只是"若 b 为 NULL 就用 0",它恰恰可能制造一个 0 作为分母:
-- 错误!b 为 NULL 时变成 0,随后除零报错
SELECT a / coalesce(b, 0) FROM t;
-- 正确
SELECT a / NULLIF(b, 0) FROM t;NULLIF 做「空字符串转 NULL」
应用层写入的 ''(空串)与"未填写"(NULL)语义上常常是一回事,但数据库里是两个值。数据清洗的第一件事就是统一成一种——nullif(trim(x), '') 就是干这个的:
SELECT
nullif(trim(' '), '') AS blank_to_null, -- NULL
nullif(trim(' ab '), '') AS trimmed, -- ab
nullif(trim(''), '') AS empty_to_null; -- NULL blank_to_null | trimmed | empty_to_null
---------------+---------+---------------
| ab |
(1 row)为什么必须配 trim:' '、'\t' 这类"看起来是空"的值不写 trim 就会被 nullif 判为有效值:
-- 不完整:只有严格空串会被归一,含空白的字符串逃逸
SELECT nullif(' ', '') AS still_blank; -- ' '(不是 NULL)配 coalesce 后形成完整的清洗链,这是数据迁移与 ETL 里的高频组合:
SELECT
id,
coalesce(nullif(trim(nickname), ''), nullif(trim(username), ''), '匿名用户') AS display_name
FROM users
ORDER BY id;在列默认值中的用法
DEFAULT 表达式不能引用其他列,但可以调用稳定函数(如 now()、current_setting)。用 COALESCE + NULLIF 可以把"未配置则回落"的逻辑直接固化到默认值里:
CREATE TABLE audit_log (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tenant text NOT NULL
DEFAULT coalesce(nullif(current_setting('app.tenant', true), ''), 'public'),
action text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);CREATE TABLEcurrent_setting('app.tenant', true) 在参数未设置时返回 NULL(第二个参数 missing_ok = true 的作用),nullif(..., '') 再把"显式设为空串"也归一为 NULL,最后由 coalesce 落到 'public'。三层合起来才是完整的兜底链。
在 INSERT 回填与 UPSERT 中的用法
COALESCE 在 UPSERT 的部分列更新中极其常用:用 excluded.col 与旧值 t.col 比较,只覆盖"新值里真正带了的列",避免把未提供的列清成 NULL。
INSERT INTO users (id, nickname, phone, email)
VALUES (1, '新昵称', NULL, 'new@example.com')
ON CONFLICT (id) DO UPDATE
SET
nickname = coalesce(excluded.nickname, users.nickname), -- 新值为 NULL 则保留旧值
phone = coalesce(excluded.phone, users.phone),
email = coalesce(excluded.email, users.email);如果希望"空串也视为未更新",把 coalesce 换成本节的清洗链即可:
INSERT INTO users (id, nickname) VALUES (1, ' ')
ON CONFLICT (id) DO UPDATE
SET nickname = coalesce(nullif(trim(excluded.nickname), ''), users.nickname);MERGE 中的同类用法
在 MERGE ... WHEN MATCHED THEN UPDATE SET 里,同样可以用 coalesce(source.col, target.col) 表达"源缺省则保留目标值"。关于 MERGE 的完整语义,参见 MERGE。
与聚合函数组合
聚合函数遇到空集时的行为不一致,COALESCE 负责抹平差异:
| 聚合函数 | 空集返回值 | 是否需要 COALESCE |
|---|---|---|
sum | NULL | 需要 |
avg / max / min | NULL | 需要 |
count(*) / count(col) | 0 | 不需要(天然返回 0) |
array_agg | NULL | 需要,兜底为空数组 '{}' |
string_agg | NULL | 需要,兜底为空串 |
-- 正确:sum 在无匹配行时返回 NULL,需要兜底为 0
SELECT coalesce(sum(amount), 0) AS total_amount
FROM orders WHERE status = 'nonexistent'; total_amount
--------------
0.00
(1 row)-- 多余:count 永不返回 NULL,coalesce 是无效包装
SELECT coalesce(count(*), 0) AS cnt FROM orders; -- 正确但没必要数组与字符串聚合的兜底则很有必要,因为下游多数代码不接受 NULL:
SELECT
u.id,
coalesce(array_agg(o.order_no ORDER BY o.created_at), '{}'::text[]) AS order_nos,
coalesce(string_agg(o.order_no, ',' ORDER BY o.created_at), '') AS order_csv
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
GROUP BY u.id
ORDER BY u.id;聚合内部的 ORDER BY 保证元素顺序稳定(详见 ORDER BY 在聚合中),COALESCE 保证空集也能得到合法值——两者是搭配使用的关系。
在表达式索引中的可用性
COALESCE 与 NULLIF 的求值只依赖参数,不依赖会话状态或随机数,因此在参数都是不可变表达式时,它们可以出现在表达式索引里。这是把「兜底后的值」当作检索键的关键手段:
-- 场景一:把 NULL 归一为 0 之后建索引,让条件能走索引
CREATE INDEX idx_products_price_eff ON products ((coalesce(price, 0)));
-- 查询必须写得与索引表达式完全一致,否则用不上索引
EXPLAIN (COSTS OFF)
SELECT id FROM products WHERE coalesce(price, 0) = 0; QUERY PLAN
----------------------------------------------------------------------
Index Scan using idx_products_price_eff on products
Index Cond: (COALESCE(price, '0'::numeric) = '0'::numeric)
(2 rows)-- 场景二:把空串归一为 NULL,实现「有效值唯一」的约束
CREATE UNIQUE INDEX uq_users_email_valid ON users ((nullif(lower(trim(email)), '')));易变输入会让索引定义被拒绝
索引表达式必须满足 IMMUTABLE。如果 COALESCE 的参数里出现 now()、random()、current_user 这类 STABLE/VOLATILE 函数,CREATE INDEX 会直接报 "functions in index expression must be marked IMMUTABLE"。判断标准是参数,不是 coalesce 本身。
反过来,也要注意在 WHERE 里包裹索引列会让普通索引失效:
-- 不推荐:普通 btree 索引无法用于 coalesce(price, 0) > 100
SELECT id FROM products WHERE coalesce(price, 0) > 100;
-- 推荐:拆成等价条件,让两个分支各自走索引
SELECT id FROM products WHERE price > 100 OR (price IS NULL AND 0 > 100);与 GREATEST / LEAST 的混淆点预告
这三个函数经常被放在一起记,但语义完全不同,混淆会写出静默错误的结果:
| 函数 | 判断依据 | greatest(1, NULL) 的结果 |
|---|---|---|
COALESCE | 是否为 NULL | —(参数是值列表,不是比较) |
GREATEST | 值的大小比较 | NULL(任一参数为 NULL 即传染) |
最容易踩的坑是把 coalesce 当成"取最大值":
-- 错误理解:以为会返回 100
SELECT coalesce(0, 100); -- 0,因为 0 不是 NULL
-- 想要取最大值应该用
SELECT greatest(0, 100); -- 100GREATEST / LEAST 的 NULL 传播规则与替代写法,将在下一节展开。
实战示例
场景:用户资料清洗 + 转化率报表 + 增量回填。
步骤 1:建表并造数据
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS campaigns;
DROP TABLE IF EXISTS products;
CREATE TABLE users (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
username text,
nickname text,
phone text,
email text
);
CREATE TABLE campaigns (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name text NOT NULL,
clicks integer NOT NULL DEFAULT 0,
orders integer NOT NULL DEFAULT 0
);
CREATE TABLE products (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
sku text NOT NULL UNIQUE,
price numeric(10,2)
);
INSERT INTO users (username, nickname, phone, email) VALUES
('zhangsan', '张三', '13800000001', 'zs@example.com'),
('lisi', NULL, NULL, 'ls@example.com'),
(' ', ' ', NULL, ' '),
(NULL, NULL, NULL, NULL);
INSERT INTO campaigns (name, clicks, orders) VALUES
('春季促销', 1200, 36),
('新品首发', 0, 0),
('会员日', 800, 12);
INSERT INTO products (sku, price) VALUES
('SKU-1001', 1299.00),
('SKU-1002', NULL);CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 4
INSERT 0 3
INSERT 0 2步骤 2:多级兜底 + 空串归一
SELECT
id,
coalesce(nullif(trim(nickname), ''), nullif(trim(username), ''), '匿名用户') AS display_name,
coalesce(nullif(trim(email), ''), nullif(trim(phone), ''), '无联系方式') AS contact
FROM users
ORDER BY id; id | display_name | contact
----+--------------+----------------
1 | 张三 | zs@example.com
2 | lisi | ls@example.com
3 | 匿名用户 | 无联系方式
4 | 匿名用户 | 无联系方式
(4 rows)第 3 行是关键:' ' 被 trim + nullif 归一为 NULL,因此落到了兜底值,而不是原样输出三个空格。
步骤 3:验证短路求值确实生效
-- 若第一个参数非 0,右侧的 20 / 0 完全不求值
SELECT
coalesce(nullif(20, 0), 20 / 0) AS short_circuit_hit, -- 20
coalesce(nullif(0, 0), 20 / 20) AS short_circuit_miss; -- 1 short_circuit_hit | short_circuit_miss
-------------------+-------------------
20 | 1
(1 row)步骤 4:转化率报表(两级兜底)
SELECT
name,
clicks,
orders,
round(orders::numeric / NULLIF(clicks, 0), 4) AS raw_rate,
coalesce(round(orders::numeric / NULLIF(clicks, 0), 4), 0) AS report_rate
FROM campaigns
ORDER BY id; name | clicks | orders | raw_rate | report_rate
----------+--------+--------+----------+-------------
春季促销 | 1200 | 36 | 0.0300 | 0.0300
新品首发 | 0 | 0 | | 0.0000
会员日 | 800 | 12 | 0.0150 | 0.0150
(3 rows)步骤 5:UPSERT 增量回填(只覆盖真正带值的列)
-- 只带了 email,nickname/phone 留空 → 保留旧值
INSERT INTO users (id, username, nickname, phone, email)
VALUES (1, 'zhangsan', NULL, NULL, 'zhangsan@new.com')
ON CONFLICT (id) DO UPDATE
SET
nickname = coalesce(nullif(trim(excluded.nickname), ''), users.nickname),
phone = coalesce(nullif(trim(excluded.phone), ''), users.phone),
email = coalesce(nullif(trim(excluded.email), ''), users.email)
RETURNING id, username, nickname, phone, email; id | username | nickname | phone | email
----+----------+----------+-------------+-------------------
1 | zhangsan | 张三 | 13800000001 | zhangsan@new.com
(1 row)nickname 与 phone 传的都是 NULL,经 coalesce 后保留了旧值;只有 email 被更新。
步骤 6:表达式索引验证
CREATE INDEX idx_products_price_eff ON products ((coalesce(price, 0)));
ANALYZE products;
EXPLAIN (COSTS OFF)
SELECT sku FROM products WHERE coalesce(price, 0) = 0; QUERY PLAN
----------------------------------------------------------------------
Index Scan using idx_products_price_eff on products
Index Cond: (COALESCE(price, '0'::numeric) = '0'::numeric)
(2 rows)步骤 7:清理
DROP TABLE users;
DROP TABLE campaigns;
DROP TABLE products;注意事项
COALESCE只判断 NULL,不判断"无效值"。空串、0、false都会原样返回,需要先NULLIF归一。
-- 错误预期:以为会得到 100
SELECT coalesce(0, 100); -- 0
-- 正确:先把 0 视为无效
SELECT coalesce(nullptr_placeholder, 100);- 参数必须先归约到同一类型,跨类别直接报错,不要依赖隐式转换去猜:
-- 错误!
SELECT coalesce(1, true); -- ERROR: COALESCE types integer and boolean cannot be matched
-- 正确
SELECT coalesce(1::text, 'true');NULLIF只有两个参数,排除多个哨兵值只能嵌套:
SELECT nullif(nullif(nullif(status, ''), 'unknown'), 'n/a') FROM t;COALESCE不能防除零,NULLIF才能:
-- 错误!
SELECT a / coalesce(b, 0) FROM t;
-- 正确
SELECT a / NULLIF(b, 0) FROM t;coalesce(count(*), 0)是无用包装,count在空集上天然返回0。真正需要兜底的是sum/avg/array_agg/string_agg。短路求值只影响执行期。类型检查与语法解析仍会遍历所有参数,
coalesce(1, true)该报错还是会报错。WHERE中包裹索引列会导致普通索引失效:
-- 不推荐:索引无法匹配
WHERE coalesce(price, 0) > 100
-- 推荐:拆成等价条件
WHERE price > 100 OR (price IS NULL AND 0 > 100)表达式索引要求参数不可变。
coalesce(now(), t.col)无法建索引;查询写法必须与索引表达式逐字一致。DEFAULT表达式不能引用其他列。想在默认值里做"多列兜底"是做不到的,只能用current_setting、常量与稳定函数。NULLIF基于=比较,字符串比较受排序规则影响。不同 collation 下大小写与重音的相等判定可能不同,跨库迁移要留意。
最佳实践
聚合结果一律用
COALESCE兜底为业务默认值,sum/avg/array_agg都适用,唯独count不必:sqlSELECT coalesce(sum(amount), 0) FROM orders; SELECT coalesce(array_agg(x ORDER BY x), '{}'::int[]) FROM t;除法一律用
NULLIF(分母, 0)包裹,这是消除线上division by zero的最低成本手段。清洗链固定写成
coalesce(nullif(trim(x), ''), 默认值),把空串与 NULL 一次归一并兜底,避免每个查询各写一套。UPSERT 部分更新用
coalesce(excluded.col, t.col),语义与代码都最清晰:sqlSET nickname = coalesce(nullif(trim(excluded.nickname), ''), users.nickname)需要"兜底后的值"参与检索时建表达式索引,并保证查询写法与索引表达式一致。
不要用嵌套
CASE WHEN a IS NOT NULL THEN a ELSE b END替代COALESCE,多级兜底时前者冗长且更易写错:sql-- 不推荐 CASE WHEN a IS NOT NULL THEN a WHEN b IS NOT NULL THEN b ELSE 'x' END -- 推荐 coalesce(a, b, 'x')把兜底默认值抽成常量或小函数,避免在几十处查询里硬编码同一个字符串:
sqlCREATE OR REPLACE FUNCTION display_name(nick text, uname text) RETURNS text LANGUAGE sql IMMUTABLE AS $$ SELECT coalesce(nullif(trim(nick), ''), nullif(trim(uname), ''), '匿名用户'); $$;把
0、空串这类哨兵值在设计阶段就消灭。列上加NOT NULL DEFAULT ''之类的约定,能减少大量运行时的NULLIF;只有在对接外部系统时才不得不做归一。
下一节
继续学习:GREATEST / LEAST