标量子查询必须返回单行单列,否则报错“more than one row returned”;常见原因包括漏LIMIT、关联缺失或未分组;推荐用LIMIT 1兜底、COALESCE处理NULL、LATERAL替代复杂聚合。

标量子查询必须返回单行单列,否则会报错
PostgreSQL 的 json_build_object() 和 json_agg() 本身不拒绝多行输入,但标量子查询(subquery in SELECT list)要求严格返回 0 或 1 行、1 列。一旦子查询返回多行,立刻报错:more than one row returned by a subquery used as an expression。
常见诱因包括:忘了加 LIMIT 1、关联条件漏写导致笛卡尔积、或误用聚合前未分组。
- 如果业务上确实需要“取最新一条”,用
(SELECT ... ORDER BY created_at DESC LIMIT 1),别只靠MAX()拿值再 JOIN —— 那是两步,不是标量 - 若想兜底空结果,外层用
COALESCE((SELECT ...), '{}'::json),避免整个行被 NULL 掉 - 子查询里禁止出现
json_agg()或string_agg()等集函数,除非已用GROUP BY确保单行输出
用 json_build_object() + 标量子查询拼字段最稳妥
直接在 SELECT 列表里写 (SELECT json_build_object('id', u.id, 'name', u.name) FROM users u WHERE u.id = o.user_id) 是可行的,但要注意:子查询里的 u.id 和 o.user_id 必须能明确关联,且 users 表对每个 o.user_id 最多只有一条匹配 —— 否则又掉进多行陷阱。
更安全的做法是把关联逻辑收进子查询内部,显式限制:
详细的 Three.js 3D 图形参考,涵盖场景设置、相机、几何体、材质、光照、动画、控制器、加载器、数学工具和调试。
SELECT
o.order_id,
(SELECT json_build_object(
'user_id', u.id,
'email', u.email,
'role', u.role
)
FROM users u
WHERE u.id = o.user_id
LIMIT 1) AS user_info
FROM orders o;
- 别在子查询里引用外部表的非关联字段(比如
o.status),PostgreSQL 8.4+ 虽支持,但可读性差、易出错 - 如果
users表有索引ON users(id),这个子查询能走索引,性能可控;没索引就可能变全表扫描 - 字段名带大小写?用双引号括起来:
'"Name"', u."Name",否则转成小写
需要数组字段时,改用 LATERAL JOIN 配合 json_agg()
标量子查询天生不支持生成 JSON 数组(因为 json_agg() 返回一行一列的 JSON 值,但前提是输入是多行结果集 —— 这和标量定义冲突)。硬要用子查询套 json_agg(),必须确保子查询只输出一行,例如:
SELECT
o.order_id,
(SELECT json_agg(json_build_object('sku', i.sku, 'qty', i.qty))
FROM order_items i
WHERE i.order_id = o.order_id) AS items
FROM orders o;
但这种写法隐含风险:如果 order_items 没有索引,或 WHERE 条件无法高效过滤,性能会断崖下跌。
- 推荐改用
LATERAL:它允许右侧子查询“感知”左侧行,且天然支持多行输出,再配json_agg()更清晰 -
LATERAL子句中可自由用ORDER BY、LIMIT、聚合,不会触发标量行数限制 - 如果某订单没有 item,
LATERAL默认返回空数组[];而标量子查询会返回NULL,需额外处理
NULL 字段会让 json_build_object() 丢键,要提前处理
json_build_object('a', NULL, 'b', 'ok') 结果是 {"b": "ok"} —— NULL 值对应的键直接消失。这在动态构造 JSON 时容易造成结构不一致,前端解析可能失败。
- 统一用
COALESCE(col, 'null'::text)或COALESCE(col::text, 'null')强制转字符串保留键 - 若想保留真正的 JSON
null值(即{"a": null, "b": "ok"}),得写成json_build_object('a', to_json(col), 'b', to_json('ok')) -
to_json()对NULL输入返回 JSONnull,而直接传NULL给json_build_object()就是删键
动态 JSON 的麻烦不在语法,而在边界:空数据、重复键、类型混杂、嵌套深度失控。标量子查询看着简单,实际每层都得验行数、控 NULL、查索引。写完务必用 EXPLAIN ANALYZE 看执行计划,尤其注意子查询有没有变成 Nested Loop 并扫全表。

















