0

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()




1 Answer 1

2

In SQLAlchemy, the SQL || operator is the Python + operator. I found this information in this Github issue.

Appending to the existing JSONB array of your example should be something like (untested):

new_records = [{"bob": {"age": 10}}, {"billy": {"age": 10}}]

stmt = (
    sa.update(TestObj)
    .filter_by(name="paul")
    .values(data=TestObj.data + new_records)  # concatenate existing data with new records
)
session.execute(stmt)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.