核心区别一览特性to_json(anyelement)row_to_json(record)输入类型任意类型标量、数组、复合类型必须是行记录record/row字段命名复合类型使用类型定义的名称匿名记录生成f1, f2, f3...NULL 处理NULL 值转为 JSON nullNULL 值转为 JSON null适用场景通用 JSON 转换专门用于表行转 JSON性能略快直接转换略慢需要构建记录一、to_json() 的妙用1. 标量值转换-- 基本数据类型SELECTto_json(123);-- 123SELECTto_json(hello);-- helloSELECTto_json(true);-- trueSELECTto_json(NULL);-- nullSELECTto_json(2026-07-04::date);-- 2026-07-042. 数组转 JSON 数组-- 整数数组SELECTto_json(ARRAY[1,2,3]);-- 输出: [1, 2, 3]-- 文本数组SELECTto_json(ARRAY[apple,banana,cherry]);-- 输出: [apple, banana, cherry]-- 嵌套数组SELECTto_json(ARRAY[ARRAY[1,2],ARRAY[3,4]]);-- 输出: [[1, 2], [3, 4]]3. 复合类型转 JSON 对象-- 定义复合类型CREATETYPEperson_typeAS(idint,nametext,ageint);-- 转换复合类型SELECTto_json(ROW(1,张三,25)::person_type);-- 输出: {id: 1, name: 张三, age: 25}关键点字段名来自类型定义而非匿名记录。4. 嵌套 JSON 结构-- 构造复杂嵌套结构SELECTto_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. 与聚合函数结合-- 将多行聚合成 JSON 数组SELECTto_json(array_agg(row_to_json(u)))FROM(SELECTid,name,emailFROMusersLIMIT3)u;-- 输出: [{id:1,name:张三,email:zhangexample.com}, ...]二、row_to_json() 的妙用1. 表行转 JSON-- 最基础用法SELECTrow_to_json(users)FROMusersWHEREid1;-- 输出: {id:1,name:张三,email:zhangexample.com,created_at:2026-01-01T00:00:00}2. 选择特定字段避免多余数据-- ❌ 错误匿名记录字段名为 f1, f2, f3SELECTrow_to_json((id,name,email))FROMusersWHEREid1;-- 输出: {f1:1,f2:张三,f3:zhangexample.com}-- ✅ 正确使用子查询保留字段名SELECTrow_to_json(t)FROM(SELECTid,name,emailFROMusersWHEREid1)t;-- 输出: {id:1,name:张三,email:zhangexample.com}3. 格式化输出调试用-- 第二个参数为 true 时输出格式化的 JSONSELECTrow_to_json(users,true)FROMusersWHEREid1;-- 输出:{id:1,name:张三,email:zhangexample.com,created_at:2026-01-01T00:00:00}注意仅用于调试生产环境不要开启增加传输体积。4. 嵌套子查询-- 订单包含订单项SELECTo.order_id,o.order_date,row_to_json(item_agg)ASitemsFROMorders oLEFTJOINLATERAL(SELECTjson_agg(row_to_json(oi))ASitemsFROMorder_items oiWHEREoi.order_ido.order_id)item_aggONtrueWHEREo.order_id1001;-- 输出:{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 数组-- 将所有用户聚合成一个 JSON 数组SELECTjson_agg(row_to_json(u))FROM(SELECTid,name,emailFROMusersORDERBYidLIMIT10)u;-- 输出: [{id:1,name:张三,...}, {id:2,name:李四,...}]三、关键区别详解区别1字段命名规则-- to_json 使用复合类型的字段名CREATETYPEuser_typeAS(user_idint,user_nametext);SELECTto_json(ROW(1,张三)::user_type);-- 输出: {user_id: 1, user_name: 张三}-- row_to_json 匿名记录会生成 f1, f2...SELECTrow_to_json((1,张三));-- 输出: {f1: 1, f2: 张三}-- row_to_json 子查询保留原始字段名SELECTrow_to_json(t)FROM(SELECT1ASuser_id,张三ASuser_name)t;-- 输出: {user_id: 1, user_name: 张三}区别2输入类型要求-- to_json 可以接受任何类型SELECTto_json(123);-- ✅ 标量SELECTto_json(ARRAY[1,2]);-- ✅ 数组SELECTto_json(ROW(1,a));-- ✅ 复合类型SELECTto_json({a:1}::jsonb);-- ✅ JSONB-- row_to_json 必须是行记录SELECTrow_to_json(123);-- ❌ 错误SELECTrow_to_json(ROW(1,a));-- ✅ 正确SELECTrow_to_json(users)FROMusers;-- ✅ 正确表行区别3性能差异-- 测试数据10 万行用户表-- to_json 略快直接转换EXPLAINANALYZESELECTto_json(ROW(id,name,email))FROMusers;-- 执行时间: ~850ms-- row_to_json 略慢需要构建记录EXPLAINANALYZESELECTrow_to_json(t)FROM(SELECTid,name,emailFROMusers)t;-- 执行时间: ~920ms-- 差异原因row_to_json 需要先构建子查询的记录结构结论小数据量差异可忽略大数据量时to_json略优。四、实战场景对比场景1API 返回单条记录-- 推荐row_to_json语义清晰SELECTrow_to_json(t)FROM(SELECTid,name,emailFROMusersWHEREid123)t;-- 也可以to_json需要显式转换类型SELECTto_json(ROW(id,name,email))FROMusersWHEREid123;场景2批量导出为 JSON 文件-- 推荐json_agg row_to_jsonCOPY(SELECTjson_agg(row_to_json(u))FROM(SELECTid,name,email,created_atFROMusersWHEREcreated_at2026-01-01)u)TO/tmp/users_export.json;场景3构造嵌套 JSON-- 方案1to_json 嵌套更灵活SELECTto_json(ROW(o.order_id,o.order_date,(SELECTjson_agg(to_json(ROW(oi.product,oi.qty,oi.price)))FROMorder_items oiWHEREoi.order_ido.order_id)));-- 方案2row_to_json LEFT JOIN LATERAL更高效SELECTrow_to_json(order_with_items)FROM(SELECTo.order_id,o.order_date,item_agg.itemsFROMorders oLEFTJOINLATERAL(SELECTjson_agg(row_to_json(oi))ASitemsFROMorder_items oiWHEREoi.order_ido.order_id)item_aggONtrue)order_with_items;场景4动态字段选择-- to_json 更适合动态 SQLDO$$DECLAREsqltext:SELECT id, ||current_setting(app.fields)|| FROM users;result json;BEGINEXECUTEformat(SELECT to_json(t) FROM (%s) t,sql)INTOresult;RAISE NOTICE%,result;END$$;-- row_to_json 需要预先知道字段名五、性能陷阱与优化陷阱1大数据量内存爆炸-- ❌ 危险一次性加载 100 万行SELECTjson_agg(row_to_json(u))FROM(SELECT*FROMlarge_table)u;-- ✅ 优化分页处理SELECTjson_agg(row_to_json(u))FROM(SELECT*FROMlarge_tableLIMIT1000OFFSET0)u;陷阱2相关子查询导致 N1 问题-- ❌ 低效每行执行一次子查询SELECTo.order_id,(SELECTjson_agg(row_to_json(oi))FROMorder_items oiWHEREoi.order_ido.order_id)ASitemsFROMorders o;-- ✅ 高效LEFT JOIN LATERALSELECTo.order_id,item_agg.itemsFROMorders oLEFTJOINLATERAL(SELECTjson_agg(row_to_json(oi))ASitemsFROMorder_items oiWHEREoi.order_ido.order_id)item_aggONtrue;陷阱3SELECT * 导致多余字段-- ❌ 浪费带宽SELECTrow_to_json(users)FROMusers;-- ✅ 只选需要的字段SELECTrow_to_json(t)FROM(SELECTid,name,email-- 明确指定FROMusers)t;六、最佳实践总结何时使用 to_json✅推荐场景转换标量值或数组使用预定义的复合类型构造动态 JSON 结构性能敏感场景略快❌避免场景直接转换表行语义不清晰需要保留原始字段名的简单查询何时使用 row_to_json✅推荐场景表行直接转 JSON语义清晰配合子查询选择特定字段嵌套聚合配合json_agg快速原型开发❌避免场景匿名记录字段名变成 f1, f2…超大数据量聚合内存爆炸高并发 API序列化开销通用优化建议始终限制返回行数LIMIT1000-- 防止意外全表扫描只选择需要的字段SELECTid,name-- 不要 SELECT *确保关联字段有索引CREATEINDEXidx_order_items_order_idONorder_items(order_id);大数据量使用分页-- 分批处理避免 OOMFORiIN0..100LOOPSELECTjson_agg(row_to_json(u))FROM(SELECT*FROMtableLIMIT1000OFFSETi*1000)u;ENDLOOP;生产环境关闭格式化row_to_json(record,false)-- 默认 false不要传 true七、快速参考卡片-- to_json 速查to_json(123)-- 标量 → JSONto_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_jsonrow_to_json灵活性⭐⭐⭐⭐⭐⭐⭐⭐⭐易用性⭐⭐⭐⭐⭐⭐⭐⭐性能⭐⭐⭐⭐⭐⭐⭐⭐⭐语义清晰度⭐⭐⭐⭐⭐⭐⭐⭐核心原则简单表行转 JSON→ 用row_to_json复杂嵌套/动态结构→ 用to_json性能敏感→ 优先to_json代码可读性→ 优先row_to_json记住好用是好用但要注意效率大数据量时务必分页处理避免内存爆炸。
PostgreSQL to_json 与 row_to_json:妙用与区别详解
核心区别一览特性to_json(anyelement)row_to_json(record)输入类型任意类型标量、数组、复合类型必须是行记录record/row字段命名复合类型使用类型定义的名称匿名记录生成f1, f2, f3...NULL 处理NULL 值转为 JSON nullNULL 值转为 JSON null适用场景通用 JSON 转换专门用于表行转 JSON性能略快直接转换略慢需要构建记录一、to_json() 的妙用1. 标量值转换-- 基本数据类型SELECTto_json(123);-- 123SELECTto_json(hello);-- helloSELECTto_json(true);-- trueSELECTto_json(NULL);-- nullSELECTto_json(2026-07-04::date);-- 2026-07-042. 数组转 JSON 数组-- 整数数组SELECTto_json(ARRAY[1,2,3]);-- 输出: [1, 2, 3]-- 文本数组SELECTto_json(ARRAY[apple,banana,cherry]);-- 输出: [apple, banana, cherry]-- 嵌套数组SELECTto_json(ARRAY[ARRAY[1,2],ARRAY[3,4]]);-- 输出: [[1, 2], [3, 4]]3. 复合类型转 JSON 对象-- 定义复合类型CREATETYPEperson_typeAS(idint,nametext,ageint);-- 转换复合类型SELECTto_json(ROW(1,张三,25)::person_type);-- 输出: {id: 1, name: 张三, age: 25}关键点字段名来自类型定义而非匿名记录。4. 嵌套 JSON 结构-- 构造复杂嵌套结构SELECTto_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. 与聚合函数结合-- 将多行聚合成 JSON 数组SELECTto_json(array_agg(row_to_json(u)))FROM(SELECTid,name,emailFROMusersLIMIT3)u;-- 输出: [{id:1,name:张三,email:zhangexample.com}, ...]二、row_to_json() 的妙用1. 表行转 JSON-- 最基础用法SELECTrow_to_json(users)FROMusersWHEREid1;-- 输出: {id:1,name:张三,email:zhangexample.com,created_at:2026-01-01T00:00:00}2. 选择特定字段避免多余数据-- ❌ 错误匿名记录字段名为 f1, f2, f3SELECTrow_to_json((id,name,email))FROMusersWHEREid1;-- 输出: {f1:1,f2:张三,f3:zhangexample.com}-- ✅ 正确使用子查询保留字段名SELECTrow_to_json(t)FROM(SELECTid,name,emailFROMusersWHEREid1)t;-- 输出: {id:1,name:张三,email:zhangexample.com}3. 格式化输出调试用-- 第二个参数为 true 时输出格式化的 JSONSELECTrow_to_json(users,true)FROMusersWHEREid1;-- 输出:{id:1,name:张三,email:zhangexample.com,created_at:2026-01-01T00:00:00}注意仅用于调试生产环境不要开启增加传输体积。4. 嵌套子查询-- 订单包含订单项SELECTo.order_id,o.order_date,row_to_json(item_agg)ASitemsFROMorders oLEFTJOINLATERAL(SELECTjson_agg(row_to_json(oi))ASitemsFROMorder_items oiWHEREoi.order_ido.order_id)item_aggONtrueWHEREo.order_id1001;-- 输出:{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 数组-- 将所有用户聚合成一个 JSON 数组SELECTjson_agg(row_to_json(u))FROM(SELECTid,name,emailFROMusersORDERBYidLIMIT10)u;-- 输出: [{id:1,name:张三,...}, {id:2,name:李四,...}]三、关键区别详解区别1字段命名规则-- to_json 使用复合类型的字段名CREATETYPEuser_typeAS(user_idint,user_nametext);SELECTto_json(ROW(1,张三)::user_type);-- 输出: {user_id: 1, user_name: 张三}-- row_to_json 匿名记录会生成 f1, f2...SELECTrow_to_json((1,张三));-- 输出: {f1: 1, f2: 张三}-- row_to_json 子查询保留原始字段名SELECTrow_to_json(t)FROM(SELECT1ASuser_id,张三ASuser_name)t;-- 输出: {user_id: 1, user_name: 张三}区别2输入类型要求-- to_json 可以接受任何类型SELECTto_json(123);-- ✅ 标量SELECTto_json(ARRAY[1,2]);-- ✅ 数组SELECTto_json(ROW(1,a));-- ✅ 复合类型SELECTto_json({a:1}::jsonb);-- ✅ JSONB-- row_to_json 必须是行记录SELECTrow_to_json(123);-- ❌ 错误SELECTrow_to_json(ROW(1,a));-- ✅ 正确SELECTrow_to_json(users)FROMusers;-- ✅ 正确表行区别3性能差异-- 测试数据10 万行用户表-- to_json 略快直接转换EXPLAINANALYZESELECTto_json(ROW(id,name,email))FROMusers;-- 执行时间: ~850ms-- row_to_json 略慢需要构建记录EXPLAINANALYZESELECTrow_to_json(t)FROM(SELECTid,name,emailFROMusers)t;-- 执行时间: ~920ms-- 差异原因row_to_json 需要先构建子查询的记录结构结论小数据量差异可忽略大数据量时to_json略优。四、实战场景对比场景1API 返回单条记录-- 推荐row_to_json语义清晰SELECTrow_to_json(t)FROM(SELECTid,name,emailFROMusersWHEREid123)t;-- 也可以to_json需要显式转换类型SELECTto_json(ROW(id,name,email))FROMusersWHEREid123;场景2批量导出为 JSON 文件-- 推荐json_agg row_to_jsonCOPY(SELECTjson_agg(row_to_json(u))FROM(SELECTid,name,email,created_atFROMusersWHEREcreated_at2026-01-01)u)TO/tmp/users_export.json;场景3构造嵌套 JSON-- 方案1to_json 嵌套更灵活SELECTto_json(ROW(o.order_id,o.order_date,(SELECTjson_agg(to_json(ROW(oi.product,oi.qty,oi.price)))FROMorder_items oiWHEREoi.order_ido.order_id)));-- 方案2row_to_json LEFT JOIN LATERAL更高效SELECTrow_to_json(order_with_items)FROM(SELECTo.order_id,o.order_date,item_agg.itemsFROMorders oLEFTJOINLATERAL(SELECTjson_agg(row_to_json(oi))ASitemsFROMorder_items oiWHEREoi.order_ido.order_id)item_aggONtrue)order_with_items;场景4动态字段选择-- to_json 更适合动态 SQLDO$$DECLAREsqltext:SELECT id, ||current_setting(app.fields)|| FROM users;result json;BEGINEXECUTEformat(SELECT to_json(t) FROM (%s) t,sql)INTOresult;RAISE NOTICE%,result;END$$;-- row_to_json 需要预先知道字段名五、性能陷阱与优化陷阱1大数据量内存爆炸-- ❌ 危险一次性加载 100 万行SELECTjson_agg(row_to_json(u))FROM(SELECT*FROMlarge_table)u;-- ✅ 优化分页处理SELECTjson_agg(row_to_json(u))FROM(SELECT*FROMlarge_tableLIMIT1000OFFSET0)u;陷阱2相关子查询导致 N1 问题-- ❌ 低效每行执行一次子查询SELECTo.order_id,(SELECTjson_agg(row_to_json(oi))FROMorder_items oiWHEREoi.order_ido.order_id)ASitemsFROMorders o;-- ✅ 高效LEFT JOIN LATERALSELECTo.order_id,item_agg.itemsFROMorders oLEFTJOINLATERAL(SELECTjson_agg(row_to_json(oi))ASitemsFROMorder_items oiWHEREoi.order_ido.order_id)item_aggONtrue;陷阱3SELECT * 导致多余字段-- ❌ 浪费带宽SELECTrow_to_json(users)FROMusers;-- ✅ 只选需要的字段SELECTrow_to_json(t)FROM(SELECTid,name,email-- 明确指定FROMusers)t;六、最佳实践总结何时使用 to_json✅推荐场景转换标量值或数组使用预定义的复合类型构造动态 JSON 结构性能敏感场景略快❌避免场景直接转换表行语义不清晰需要保留原始字段名的简单查询何时使用 row_to_json✅推荐场景表行直接转 JSON语义清晰配合子查询选择特定字段嵌套聚合配合json_agg快速原型开发❌避免场景匿名记录字段名变成 f1, f2…超大数据量聚合内存爆炸高并发 API序列化开销通用优化建议始终限制返回行数LIMIT1000-- 防止意外全表扫描只选择需要的字段SELECTid,name-- 不要 SELECT *确保关联字段有索引CREATEINDEXidx_order_items_order_idONorder_items(order_id);大数据量使用分页-- 分批处理避免 OOMFORiIN0..100LOOPSELECTjson_agg(row_to_json(u))FROM(SELECT*FROMtableLIMIT1000OFFSETi*1000)u;ENDLOOP;生产环境关闭格式化row_to_json(record,false)-- 默认 false不要传 true七、快速参考卡片-- to_json 速查to_json(123)-- 标量 → JSONto_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_jsonrow_to_json灵活性⭐⭐⭐⭐⭐⭐⭐⭐⭐易用性⭐⭐⭐⭐⭐⭐⭐⭐性能⭐⭐⭐⭐⭐⭐⭐⭐⭐语义清晰度⭐⭐⭐⭐⭐⭐⭐⭐核心原则简单表行转 JSON→ 用row_to_json复杂嵌套/动态结构→ 用to_json性能敏感→ 优先to_json代码可读性→ 优先row_to_json记住好用是好用但要注意效率大数据量时务必分页处理避免内存爆炸。