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

PostgreSQL一对多关系下的复杂批量更新与新增查询实现

搞定用户书籍库存更新+新增的PostgreSQL实现方案

我来给你梳理下怎么处理这个场景——既要更新已有书籍的库存,又要新增用户没有的书籍,还得保证操作的原子性。

核心思路

咱们可以用PostgreSQL的jsonb类型接收请求体的完整数据,分两步处理:先批量更新已有书籍的库存,再插入那些不带book_id的新增书籍。而且PostgreSQL的PL/pgSQL函数天生自带事务性,完全不用额外操心。

步骤1:编写处理函数

下面是完整的PL/pgSQL函数实现,它会自动区分需要更新的书籍和需要新增的书籍:

CREATE OR REPLACE FUNCTION update_user_books(user_data jsonb)
RETURNS void AS $$
DECLARE
    v_user_id INT := (user_data->>'user_id')::INT;
BEGIN
    -- 批量更新已有书籍的库存(带book_id的条目)
    UPDATE users_books ub
    SET stock = b.stock
    FROM (
        SELECT
            (book->>'book_id')::INT AS book_id,
            (book->>'stock')::INT AS stock
        FROM jsonb_array_elements(user_data->'books') AS book
        WHERE book ? 'book_id' -- 筛选出带book_id的更新条目
    ) b
    WHERE ub.user_id = v_user_id AND ub.book_id = b.book_id;

    -- 插入新增书籍(不带book_id的条目)
    -- 先往books表插新书拿到book_id,再关联到users_books
    INSERT INTO users_books(user_id, book_id, stock)
    SELECT
        v_user_id,
        (INSERT INTO books(name) VALUES ((book->>'name')::TEXT) RETURNING book_id),
        (book->>'stock')::INT
    FROM jsonb_array_elements(user_data->'books') AS book
    WHERE NOT book ? 'book_id'; -- 筛选出不带book_id的新增条目

END;
$$ LANGUAGE plpgsql VOLATILE;

步骤2:如何判断用户是否拥有某本书?

在更新逻辑里,我们通过WHERE ub.user_id = v_user_id AND ub.book_id = b.book_id来匹配用户已有的书籍:

  • 如果用户确实拥有这本书,就会更新库存;
  • 如果用户没有这本书,这条UPDATE语句不会有任何影响(不会报错,也不会创建新记录),完全符合咱们的需求。

要是你想对“用户未拥有的book_id”做提示,可以在更新后加个行数检查:

-- 在批量更新后添加这段代码
DECLARE
    v_updated_rows INT;
BEGIN
    -- ... 批量更新代码 ...
    GET DIAGNOSTICS v_updated_rows = ROW_COUNT;
    IF v_updated_rows < (SELECT COUNT(*) FROM jsonb_array_elements(user_data->'books') WHERE book ? 'book_id') THEN
        RAISE NOTICE '部分book_id对应用户未拥有,未执行更新';
        -- 也可以抛出中断事务的异常:RAISE EXCEPTION '存在未匹配的book_id';
    END IF;

步骤3:事务性保障

PL/pgSQL函数默认是原子性事务:函数里的所有SQL语句要么全部执行成功,要么全部回滚。比如如果插入新书时违反了books表的约束,或者类型转换出错,整个函数的操作都会被回滚,不会出现“更新了部分书籍,新增失败”的半完成状态,完全满足你的事务性要求。

调用函数的方式

直接把请求体转成jsonb传入函数就行:

SELECT update_user_books('{
  "user_id": 1,
  "name": "Ryan",
  "books": [
    {"book_id": 1, "stock": 500},
    {"book_id": 2, "stock": 500},
    {"name": "My new book 1", "stock": 500}
  ]
}'::jsonb);

性能优化建议

如果需要处理大量书籍,上面的批量更新比循环处理高效得多。如果你的业务场景里新增书籍的频率很高,还可以考虑把书籍名称的查重逻辑加进去(避免重复插入同名书籍),比如在插入前检查books表是否已存在同名书籍:

-- 修改新增书籍的查询逻辑,先查是否已有同名书
INSERT INTO users_books(user_id, book_id, stock)
SELECT
    v_user_id,
    COALESCE(
        (SELECT book_id FROM books WHERE name = (book->>'name')::TEXT),
        (INSERT INTO books(name) VALUES ((book->>'name')::TEXT) RETURNING book_id)
    ),
    (book->>'stock')::INT
FROM jsonb_array_elements(user_data->'books') AS book
WHERE NOT book ? 'book_id';

内容的提问来源于stack exchange,提问作者Élisa Plessis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:14:13