I have a postgres DB with a jsonb field data.
I'd like to be able to append to an array of items in the field using sqlalchemy.
For example with the table:
| id | data | name | |
|---|---|---|---|
| 1 | [{"jon": {"age": 4}}, {"jane": {"age": 7}}] | paul | |
| 2 | [{"beryl": {"age": 3}}, {"victor": {"age": 9}}] | dave | |
To do this in postgres I can use concatenate like this:
UPDATE "test" SET "data" = "data" || '[{"beryl": {"age": 3}}, {"victor": {"age": 9}}]' ::jsonb
WHERE "name"='paul';
Giving:
| id | data | name | |
|---|---|---|---|
| 1 | [{"jon": {"age": 4}}, {"jane": {"age": 7}}, {"beryl": {"age": 3}}, {"victor": {"age": 9}}] | paul | |
I've tried using jsonb_insert in sqlalchemy, but I'm not clear on how to set the path.
I don't want to add the array at a particular key, but want to add to the existing array.
EDIT
The following nests another array inside my existing one, i.e. in the form ['A', 'B'] becomes ['A','B',['C','D']]. I want this to be ['A','B','C','D']
func.jsonb_insert(
cast(TestObj.data, JSONB), "{-1}", cast(new_records, JSONB), True
)
EDIT 2
Using synchronize_session="fetch" and adding the records in a loop works, but I'm not sure it would be very efficient for a lot of records.
Grateful for any ideas on how to improve this.
for rec in new_records:
session.query(TestObj).filter(TestObj.name == "paul").update(
{
"data": func.jsonb_insert(
cast(TestObj.data, JSONB), "{-1}", cast(rec, JSONB), True
)
},
synchronize_session="fetch",
)
Full example code:
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 sqlalchemy.dialects import postgresql
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"}
initial_data = [
{
"jon": {"age": 4},
},
{"jane": {"age": 7}},
]
newentry = {"data": initial_data, "name": "paul"}
stmt = postgresql.insert(TestObj).values(newentry)
result = session.execute(stmt)
session.commit()
new_records = [{"bob": {"age": 10}}, {"billy": {"age": 10}}]
for rec in new_records:
session.query(TestObj).filter(TestObj.name == "paul").update(
{
"data": func.jsonb_insert(
cast(TestObj.data, JSONB), "{-1}", cast(rec, JSONB), True
)
},
synchronize_session="fetch",
)
session.commit()