PostgreSQL to_json 与 row_to_json:妙用与区别详解

核心区别一览

特性 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?

推荐场景:

  1. 转换标量值或数组
  2. 使用预定义的复合类型
  3. 构造动态 JSON 结构
  4. 性能敏感场景(略快)

避免场景:

  1. 直接转换表行(语义不清晰)
  2. 需要保留原始字段名的简单查询

何时使用 row_to_json?

推荐场景:

  1. 表行直接转 JSON(语义清晰)
  2. 配合子查询选择特定字段
  3. 嵌套聚合(配合 json_agg
  4. 快速原型开发

避免场景:

  1. 匿名记录(字段名变成 f1, f2...)
  2. 超大数据量聚合(内存爆炸)
  3. 高并发 API(序列化开销)

通用优化建议

  1. 始终限制返回行数

    sql 复制代码
    LIMIT 1000  -- 防止意外全表扫描
  2. 只选择需要的字段

    sql 复制代码
    SELECT id, name  -- 不要 SELECT *
  3. 确保关联字段有索引

    sql 复制代码
    CREATE INDEX idx_order_items_order_id ON order_items(order_id);
  4. 大数据量使用分页

    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;
  5. 生产环境关闭格式化

    sql 复制代码
    row_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

记住:好用是好用,但要注意效率! 大数据量时务必分页处理,避免内存爆炸。

相关推荐
我会尽全力 乐观而坚强2 小时前
MySQL库与表的操作
linux·数据库·mysql
lilihuigz2 小时前
产品附加选项插件WooCommerce Product Add-Ons:9种字段类型与3种定价模式提升销售 - 易服客工作室
数据库·mysql·wordpress插件·woocommerce扩展·定制选项
360智汇云3 小时前
Valkey 9.1上线:从Redis兼容到AI数据能力探索
数据库
标致的自行车3 小时前
Entity Framework之问题收集
jvm·数据库·oracle
啵啵鱼爱吃小猫咪3 小时前
Franka机械臂安装实时内核
数据库·postgresql
今天也是元气满满的一天呢4 小时前
从单机架构到负载均衡的互联网架构全流程
数据库·系统架构
SelectDB4 小时前
快手 AB 场景提速 145 倍,从 Spark 到 Apache Doris 的加速实践
数据库·spark·开源
SelectDB4 小时前
阶跃星辰 Agent 可观测实践:为什么 Trace 数据底座选择 SelectDB?
大数据·数据库·agent
冷静的楼房4 小时前
使用ThreadPool发起同步的调用
数据库
一个天蝎座 白勺 程序猿4 小时前
自动SQL优化实战|吃透调优接口+报告配置+统计+索引全流程落地
数据库·sql·sql优化