JSON_BUILD_OBJECT会保留NULL值为JSON null而非跳过,键名必须显式写死不可动态传入,嵌套需用子查询或CTE,性能敏感场景应慎用。

JSON_BUILD_OBJECT 会把 NULL 值转成 JSON null,不是跳过
这是最常被忽略的行为:只要列值是 NULL,JSON_BUILD_OBJECT 就会在结果里保留该字段,并赋值为 JSON 的 null,而不是省略它。比如:
SELECT JSON_BUILD_OBJECT('id', 1, 'name', NULL, 'age', 25);
结果是:{"id": 1, "name": null, "age": 25} —— 不是 {"id": 1, "age": 25}。
如果你需要过滤掉空字段,得提前用 CASE 或 NULLIF 处理,例如:
SELECT JSON_BUILD_OBJECT( 'id', id, 'name', CASE WHEN name IS NOT NULL THEN name END, 'age', age ) FROM users;
列名必须显式写死,不能动态传入字段名
JSON_BUILD_OBJECT 不接受字符串拼接的键名,下面这样会报错:
-- ❌ 错误:键名必须是字符串字面量或表达式,但不能是变量拼接
SELECT JSON_BUILD_OBJECT(col_name, value) FROM (VALUES ('id', 1)) AS t(col_name, value);
如果真要动态键名(比如从另一张表读配置),得用 json_object_agg 配合子查询,或者拼 SQL 字符串后用 EXECUTE(仅函数内)。日常多数场景,老老实实写死键名更安全、可读性更好。
常见误用点:
- 试图用
USING或别名绕过键名硬编码 —— 不支持 - 把字段别名当 JSON 键名用(如
SELECT JSON_BUILD_OBJECT('name', name AS my_name))——AS在这里无效
嵌套 JSON 对象要用子查询或 CTE,不能直接套函数
想生成类似 {"user": {"id": 1, "name": "Alice"}, "tags": ["a","b"]} 这种结构,不能靠多层 JSON_BUILD_OBJECT 直接嵌套调用(语法上虽可行,但易读性差、难维护)。
推荐写法是拆开:用子查询或 CTE 先构造内层 JSON,再包外层:
SELECT JSON_BUILD_OBJECT(
'user', (
SELECT JSON_BUILD_OBJECT('id', u.id, 'name', u.name)
FROM users u WHERE u.id = 1
),
'tags', (
SELECT JSON_AGG(t.tag)
FROM user_tags t WHERE t.user_id = 1
)
);
注意两点:
- 子查询必须加括号,且最多返回一行一列,否则报错
- 如果子查询可能无结果,外层字段会是
null(同第一条),必要时加COALESCE(..., '{}'::json)
性能敏感场景慎用 JSON_BUILD_OBJECT + 大量行
对每行都调用 JSON_BUILD_OBJECT 是 CPU 密集型操作,尤其字段多、字符串长时。实测 10 万行、5 字段组合,比纯 SELECT * 慢 3–5 倍。
优化建议:
- 只在应用层真正需要 JSON 格式时才用,不要当成通用“格式化输出”手段
- 避免在 WHERE 条件里对
JSON_BUILD_OBJECT结果做运算(如->>'name' = 'xxx'),这会强制全表计算 JSON - 若需频繁按 JSON 内字段查,优先建生成列 + 索引,例如:
ALTER TABLE users ADD COLUMN data jsonb GENERATED ALWAYS AS (JSON_BUILD_OBJECT('id', id, 'name', name)) STORED;
函数本身不走索引,也几乎无法被查询规划器下推,这点容易被低估。











