You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PostgreSQL中优化JSON批量插入并返回插入ID的精简实现

问题说明

现有language表结构如下:

lan_id INT_2 IDENTITY,
lan_code TEXT

需要创建一个接收JSON字符串的数据库函数,将数据插入language表并返回插入的ID,JSON中的sort_position字段可唯一标识每条记录。示例JSON如下:

'[
  {"id": null, "name": "Farsi", "sort_position": 5},
  {"id": null, "name": "Thai", "sort_position": 8}
]'

目前已有实现代码,但结构冗余,希望找到更紧凑的写法(比如无需拆分多个独立语句完成插入和临时表更新),现有代码如下:

CREATE TEMP TABLE language_to_insert AS
SELECT *
FROM json_populate_recordset(null::record, '[{"id": null, "name": "Farsi", "sort_position": 5}, {"id": null, "name": "Thai", "sort_position": 8}]') AS record_data
(
    id INT2,
    name TEXT,
    sort_position INT4
);

CREATE TEMP TABLE tmp_language (lan_id INT2);
WITH language_inserted AS (
    INSERT INTO language(lan_name)
    SELECT language_to_insert.name
    FROM language_to_insert
    WHERE language_to_insert.id IS NULL
    RETURNING language.lan_id
)
INSERT INTO tmp_language
SELECT language_inserted.lan_id FROM language_inserted;

-- 更新JSON输入对应的id为插入后的lan_id
WITH match_rows AS (
    SELECT t1.sort_position, t2.lan_id
    FROM (SELECT language_to_insert.sort_position, row_number() over() rn1 FROM language_to_insert WHERE language_to_insert.id IS NULL) t1
    JOIN (SELECT tmp_language.lan_id, row_number() over() rn2 FROM tmp_language) t2
    ON rn1 = rn2
)
UPDATE language_to_insert
SET id = match_rows.lan_id
FROM match_rows
WHERE language_to_insert.sort_position=match_rows.sort_position;

select * from language_to_insert;
优化后的紧凑写法

可以通过CTE(公共表表达式)整合逻辑,省去中间临时表,同时利用sort_position唯一的特性直接关联数据,无需通过行号匹配,以下是两种优化方案:

方案一:保留单临时表,合并插入与更新

-- 解析JSON到临时表
CREATE TEMP TABLE language_to_insert AS
SELECT *
FROM json_populate_recordset(null::record, '[{"id": null, "name": "Farsi", "sort_position": 5}, {"id": null, "name": "Thai", "sort_position": 8}]') AS record_data
(
    id INT2,
    name TEXT,
    sort_position INT4
);

-- 插入数据的同时直接更新临时表的id字段,一步完成
WITH inserted AS (
    INSERT INTO language(lan_name)
    SELECT name
    FROM language_to_insert
    WHERE id IS NULL
    -- 关联原临时表,返回插入的lan_id和对应的sort_position
    RETURNING lan_id, (SELECT sort_position FROM language_to_insert WHERE name = language.lan_name) AS sort_position
)
UPDATE language_to_insert
SET id = inserted.lan_id
FROM inserted
WHERE language_to_insert.sort_position = inserted.sort_position;

-- 返回最终结果
SELECT * FROM language_to_insert;

方案二:完全去掉临时表,用CTE串联所有逻辑

如果不需要保留临时表,可以直接用CTE完成JSON解析、数据插入和结果返回:

WITH parsed_data AS (
    -- 解析输入的JSON数据
    SELECT *
    FROM json_populate_recordset(null::record, '[{"id": null, "name": "Farsi", "sort_position": 5}, {"id": null, "name": "Thai", "sort_position": 8}]') AS record_data
    (
        id INT2,
        name TEXT,
        sort_position INT4
    )
), inserted_data AS (
    -- 插入数据并返回lan_id和对应的sort_position
    INSERT INTO language(lan_name)
    SELECT pd.name
    FROM parsed_data pd
    WHERE pd.id IS NULL
    RETURNING lan_id, pd.sort_position
    FROM parsed_data pd
    WHERE pd.name = language.lan_name
)
-- 组合原数据与插入后的ID,返回结果
SELECT 
    COALESCE(id_data.lan_id, pd.id) AS id,
    pd.name,
    pd.sort_position
FROM parsed_data pd
LEFT JOIN inserted_data id_data ON pd.sort_position = id_data.sort_position;

注意事项

如果name字段存在重复值,方案一中的子查询可能出现异常,此时建议使用方案二的关联方式,确保lan_id和sort_position的对应关系准确。

内容的提问来源于stack exchange,提问作者Francis Huang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 20:53:16