核心区别一览
| 特性 | to_json(anyelement) |
row_to_json(record) |
|---|---|---|
| 输入类型 | 任意类型(标量、数组、复合类型) | 必须是行记录(record/row) |
| 字段命名 | 复合类型使用类型定义的名称 | 匿名记录生成 f1, f2, f3... |
| NULL 处理 | NULL 值转为 JSON null | NULL 值转为 JSON null |
| 适用场景 | 通用 JSON 转换 | 专门用于表行转 JSON |
| 性能 | 略快(直接转换) | 略慢(需要构建记录) |
一、to_json() 的妙用
1. 标量值转换
sql
-- 基本数据类型
SELECT to_json(123); -- 123
SELECT to_json('hello'); -- "hello"
SELECT to_json(true); -- true
SELECT to_json(NULL); -- null
SELECT to_json('2026-07-04'::date); -- "2026-07-04"
2. 数组转 JSON 数组
sql
-- 整数数组
SELECT to_json(ARRAY[1, 2, 3]);
-- 输出: [1, 2, 3]
-- 文本数组
SELECT to_json(ARRAY['apple', 'banana', 'cherry']);
-- 输出: ["apple", "banana", "cherry"]
-- 嵌套数组
SELECT to_json(ARRAY[ARRAY[1, 2], ARRAY[3, 4]]);
-- 输出: [[1, 2], [3, 4]]
3. 复合类型转 JSON 对象
sql
-- 定义复合类型
CREATE TYPE person_type AS (
id int,
name text,
age int
);
-- 转换复合类型
SELECT to_json(ROW(1, '张三', 25)::person_type);
-- 输出: {"id": 1, "name": "张三", "age": 25}
关键点: 字段名来自类型定义,而非匿名记录。
4. 嵌套 JSON 结构
sql
-- 构造复杂嵌套结构
SELECT to_json(ROW(
1001,
'Order #1001',
ARRAY[
to_json(ROW('iPhone', 1, 999)::product_type),
to_json(ROW('AirPods', 2, 199)::product_type)
]
)::order_type);
-- 输出:
{
"order_id": 1001,
"order_name": "Order #1001",
"items": [
{"product": "iPhone", "qty": 1, "price": 999},
{"product": "AirPods", "qty": 2, "price": 199}
]
}
5. 与聚合函数结合
sql
-- 将多行聚合成 JSON 数组
SELECT to_json(array_agg(row_to_json(u)))
FROM (
SELECT id, name, email
FROM users
LIMIT 3
) u;
-- 输出: [{"id":1,"name":"张三","email":"zhang@example.com"}, ...]
二、row_to_json() 的妙用
1. 表行转 JSON
sql
-- 最基础用法
SELECT row_to_json(users)
FROM users
WHERE id = 1;
-- 输出: {"id":1,"name":"张三","email":"zhang@example.com","created_at":"2026-01-01T00:00:00"}
2. 选择特定字段(避免多余数据)
sql
-- ❌ 错误:匿名记录字段名为 f1, f2, f3
SELECT row_to_json((id, name, email))
FROM users
WHERE id = 1;
-- 输出: {"f1":1,"f2":"张三","f3":"zhang@example.com"}
-- ✅ 正确:使用子查询保留字段名
SELECT row_to_json(t)
FROM (
SELECT id, name, email
FROM users
WHERE id = 1
) t;
-- 输出: {"id":1,"name":"张三","email":"zhang@example.com"}
3. 格式化输出(调试用)
sql
-- 第二个参数为 true 时,输出格式化的 JSON
SELECT row_to_json(users, true)
FROM users
WHERE id = 1;
-- 输出:
{
"id" : 1,
"name" : "张三",
"email" : "zhang@example.com",
"created_at" : "2026-01-01T00:00:00"
}
注意: 仅用于调试,生产环境不要开启(增加传输体积)。
4. 嵌套子查询
sql
-- 订单包含订单项
SELECT
o.order_id,
o.order_date,
row_to_json(item_agg) AS items
FROM orders o
LEFT JOIN LATERAL (
SELECT json_agg(row_to_json(oi)) AS items
FROM order_items oi
WHERE oi.order_id = o.order_id
) item_agg ON true
WHERE o.order_id = 1001;
-- 输出:
{
"order_id": 1001,
"order_date": "2026-07-04",
"items": {
"items": [
{"item_id": 1, "product": "iPhone", "qty": 1},
{"item_id": 2, "product": "AirPods", "qty": 2}
]
}
}
5. 聚合为 JSON 数组
sql
-- 将所有用户聚合成一个 JSON 数组
SELECT json_agg(row_to_json(u))
FROM (
SELECT id, name, email
FROM users
ORDER BY id
LIMIT 10
) u;
-- 输出: [{"id":1,"name":"张三",...}, {"id":2,"name":"李四",...}]
三、关键区别详解
区别1:字段命名规则
sql
-- to_json 使用复合类型的字段名
CREATE TYPE user_type AS (user_id int, user_name text);
SELECT to_json(ROW(1, '张三')::user_type);
-- 输出: {"user_id": 1, "user_name": "张三"}
-- row_to_json 匿名记录会生成 f1, f2...
SELECT row_to_json((1, '张三'));
-- 输出: {"f1": 1, "f2": "张三"}
-- row_to_json 子查询保留原始字段名
SELECT row_to_json(t) FROM (SELECT 1 AS user_id, '张三' AS user_name) t;
-- 输出: {"user_id": 1, "user_name": "张三"}
区别2:输入类型要求
sql
-- to_json 可以接受任何类型
SELECT to_json(123); -- ✅ 标量
SELECT to_json(ARRAY[1, 2]); -- ✅ 数组
SELECT to_json(ROW(1, 'a')); -- ✅ 复合类型
SELECT to_json('{"a":1}'::jsonb); -- ✅ JSONB
-- row_to_json 必须是行记录
SELECT row_to_json(123); -- ❌ 错误!
SELECT row_to_json(ROW(1, 'a')); -- ✅ 正确
SELECT row_to_json(users) FROM users; -- ✅ 正确(表行)
区别3:性能差异
sql
-- 测试数据:10 万行用户表
-- to_json 略快(直接转换)
EXPLAIN ANALYZE
SELECT to_json(ROW(id, name, email))
FROM users;
-- 执行时间: ~850ms
-- row_to_json 略慢(需要构建记录)
EXPLAIN ANALYZE
SELECT row_to_json(t)
FROM (SELECT id, name, email FROM users) t;
-- 执行时间: ~920ms
-- 差异原因:row_to_json 需要先构建子查询的记录结构
结论: 小数据量差异可忽略,大数据量时 to_json 略优。
四、实战场景对比
场景1:API 返回单条记录
sql
-- 推荐:row_to_json(语义清晰)
SELECT row_to_json(t)
FROM (
SELECT id, name, email
FROM users
WHERE id = 123
) t;
-- 也可以:to_json(需要显式转换类型)
SELECT to_json(ROW(id, name, email))
FROM users
WHERE id = 123;
场景2:批量导出为 JSON 文件
sql
-- 推荐:json_agg + row_to_json
COPY (
SELECT json_agg(row_to_json(u))
FROM (
SELECT id, name, email, created_at
FROM users
WHERE created_at >= '2026-01-01'
) u
) TO '/tmp/users_export.json';
场景3:构造嵌套 JSON
sql
-- 方案1:to_json 嵌套(更灵活)
SELECT to_json(ROW(
o.order_id,
o.order_date,
(
SELECT json_agg(to_json(ROW(oi.product, oi.qty, oi.price)))
FROM order_items oi
WHERE oi.order_id = o.order_id
)
));
-- 方案2:row_to_json + LEFT JOIN LATERAL(更高效)
SELECT row_to_json(order_with_items)
FROM (
SELECT
o.order_id,
o.order_date,
item_agg.items
FROM orders o
LEFT JOIN LATERAL (
SELECT json_agg(row_to_json(oi)) AS items
FROM order_items oi
WHERE oi.order_id = o.order_id
) item_agg ON true
) order_with_items;
场景4:动态字段选择
sql
-- to_json 更适合动态 SQL
DO $$
DECLARE
sql text := 'SELECT id, ' || current_setting('app.fields') || ' FROM users';
result json;
BEGIN
EXECUTE format('SELECT to_json(t) FROM (%s) t', sql) INTO result;
RAISE NOTICE '%', result;
END $$;
-- row_to_json 需要预先知道字段名
五、性能陷阱与优化
陷阱1:大数据量内存爆炸
sql
-- ❌ 危险:一次性加载 100 万行
SELECT json_agg(row_to_json(u))
FROM (SELECT * FROM large_table) u;
-- ✅ 优化:分页处理
SELECT json_agg(row_to_json(u))
FROM (
SELECT * FROM large_table
LIMIT 1000 OFFSET 0
) u;
陷阱2:相关子查询导致 N+1 问题
sql
-- ❌ 低效:每行执行一次子查询
SELECT
o.order_id,
(SELECT json_agg(row_to_json(oi))
FROM order_items oi
WHERE oi.order_id = o.order_id) AS items
FROM orders o;
-- ✅ 高效:LEFT JOIN LATERAL
SELECT
o.order_id,
item_agg.items
FROM orders o
LEFT JOIN LATERAL (
SELECT json_agg(row_to_json(oi)) AS items
FROM order_items oi
WHERE oi.order_id = o.order_id
) item_agg ON true;
陷阱3:SELECT * 导致多余字段
sql
-- ❌ 浪费带宽
SELECT row_to_json(users) FROM users;
-- ✅ 只选需要的字段
SELECT row_to_json(t)
FROM (
SELECT id, name, email -- 明确指定
FROM users
) t;
六、最佳实践总结
何时使用 to_json?
✅ 推荐场景:
- 转换标量值或数组
- 使用预定义的复合类型
- 构造动态 JSON 结构
- 性能敏感场景(略快)
❌ 避免场景:
- 直接转换表行(语义不清晰)
- 需要保留原始字段名的简单查询
何时使用 row_to_json?
✅ 推荐场景:
- 表行直接转 JSON(语义清晰)
- 配合子查询选择特定字段
- 嵌套聚合(配合
json_agg) - 快速原型开发
❌ 避免场景:
- 匿名记录(字段名变成 f1, f2...)
- 超大数据量聚合(内存爆炸)
- 高并发 API(序列化开销)
通用优化建议
-
始终限制返回行数
sqlLIMIT 1000 -- 防止意外全表扫描 -
只选择需要的字段
sqlSELECT id, name -- 不要 SELECT * -
确保关联字段有索引
sqlCREATE INDEX idx_order_items_order_id ON order_items(order_id); -
大数据量使用分页
sql-- 分批处理,避免 OOM FOR i IN 0..100 LOOP SELECT json_agg(row_to_json(u)) FROM (SELECT * FROM table LIMIT 1000 OFFSET i*1000) u; END LOOP; -
生产环境关闭格式化
sqlrow_to_json(record, false) -- 默认 false,不要传 true
七、快速参考卡片
sql
-- to_json 速查
to_json(123) -- 标量 → JSON
to_json(ARRAY[1,2]) -- 数组 → JSON 数组
to_json(ROW(1,'a')::my_type) -- 复合类型 → JSON 对象
-- row_to_json 速查
row_to_json(users) -- 表行 → JSON 对象
row_to_json(t) FROM (SELECT ...) t -- 子查询 → JSON 对象
row_to_json(record, true) -- 格式化输出(调试用)
-- 聚合速查
json_agg(row_to_json(u)) -- 多行 → JSON 数组
json_build_object('key', value) -- 手动构造 JSON 对象
json_build_array(1, 'a', true) -- 手动构造 JSON 数组
总结
| 维度 | to_json | row_to_json |
|---|---|---|
| 灵活性 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 易用性 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 语义清晰度 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
核心原则:
- 🎯 简单表行转 JSON → 用
row_to_json - 🎯 复杂嵌套/动态结构 → 用
to_json - 🎯 性能敏感 → 优先
to_json - 🎯 代码可读性 → 优先
row_to_json
记住:好用是好用,但要注意效率! 大数据量时务必分页处理,避免内存爆炸。