SQLAlchemy 在 postgres 中添加/更新 jsonb 字段

SQLAlchemy adding / updating jsonb field in postgres

我正在尝试使用 SQLAlchemy 更新 postgres 数据库中 jsonb 字段中的值。

一直在尝试使用 func.jsonb_set,但我不太清楚如何实现它。

使用如下所示的 table (test),我希望找到一种添加/编辑 json 数据的通用方法。

id data name
1 {"age": 44, "name": "barry", children": ["baz", "jim"]} barry
2 {"age": 47, "name": "dave", "children": ["jeff", "jane"]} dave

以下在 postgres 中进行简单更新。

UPDATE "test" SET "data"=jsonb_set("data"::jsonb, '{age}', '45')
WHERE "data"::json->>'name'='dave';

我可以使用 update 更新单个值,如下所示:

testobj_res.update(
    {
        TestObj.data: cast(
            cast(TestObj.data, JSONB).concat(func.jsonb_build_object("age", 45)),
            JSON,
        )
    }
)

session.commit()

我希望能够传递多个字段的更新,例如{"name": "barry", "age": 45, "height": 150}.

我尝试使用 func.jsonb_set 来添加更复杂的 json 结构而不是 ('age', 45)

testobj_res.first().data = func.jsonb_set(
    TestObj.data.cast(JSONB),
    ("age", 45),
    cast(TestObj.data, JSONB))

session.commit()

但我得到:

sqlalchemy.exc.ProgrammingError: (psycopg2.errors.UndefinedFunction) function jsonb_set(jsonb, record, jsonb) does not exist
LINE 1: UPDATE public.test SET data=jsonb_set(CAST(public.test.data ...
                                    ^
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.

完整示例代码:

import os
from sqlalchemy.dialects.postgresql import JSON, JSONB
from sqlalchemy import func, cast
import sqlalchemy as sa
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import urllib
from dotenv import load_dotenv

load_dotenv()

user = urllib.parse.quote_plus(os.environ.get("DB_USER"))
passwd = urllib.parse.quote_plus(os.environ.get("DB_PW"))

DB_URL = "postgresql://{}:{}@{}:{}/{}".format(
    user,
    passwd,
    os.environ.get("DB_HOST"),
    os.environ.get("DB_PORT"),
    os.environ.get("DB_NAME"),
)

engine = sa.create_engine(DB_URL)
Session = sessionmaker(bind=engine, autoflush=True)

session = Session()

Base = declarative_base()


class TestObj(Base):
    __tablename__ = "test"
    __table_args__ = {"autoload_with": engine, "schema": "public"}


testobj_res = session.query(TestObj).filter(TestObj.name == "dave")

testobj_res.first().data = func.jsonb_set(
    TestObj.data.cast(JSONB),
    ("age", 45),
    cast(TestObj.data, JSONB))

session.commit()

这个代码

testobj_res.first().data = func.jsonb_set(
    TestObj.data.cast(JSONB),
    ("age", 45),
    cast(TestObj.data, JSONB))

将一个 JSONB 对象、record 和另一个 JSONB 对象传递给 jsonb_set,但该函数需要一个 JSONB 对象、一个 JSONPath 字符串和路径的新值作为 JSONB*.

解压 ('age', 45) 元组并删除最终的 JSONB 将产生所需的结果。

with Session.begin() as s:
    testobj = s.scalar(sa.select(TestObj).limit(1))
    testobj.data = sa.func.jsonb_set(
        sa.cast(testobj.data, postgresql.JSONB),
        '{age}',
        sa.cast(42, postgresql.JSONB)
    )

* 还有一个可选的布尔值 create-if-not-exists 参数,我们可以忽略它。