Working with Multiple Entries¶
This script inserts multiple entries into our two tables in one session & relies on in-session auto-incrementing behavior. As mentioned in the Known Limitations, SQLAlchemy is slower than other drivers at performing multiple entry inserts.
This example is provided to show how a multiple insert would work for two tables the
rely upon auto-incrementation of the IDs. In particular, note that in 1.a and 1.b
the difference between SQLAlchemy-Exasol and other SQLAlchemy dialects. For some other
SQLAlchemy dialects, they could use something like:
users = session.scalars(
insert(User).returning(User),
bulk_data
).all()
This, however, does not work for Exasol and will result in:
sqlalchemy.exc.InvalidRequestError: Can't use explicit RETURNING for bulk INSERT
operation with exasol+exasol.driver.websocket.dbapi2 backend; executemany with
RETURNING is not enabled for this dialect.
examples/features/orm/_2_working_with_multiple_entries.py¶
from sqlalchemy import (
delete,
insert,
select,
tuple_,
)
from sqlalchemy.orm import (
Session,
joinedload,
)
from examples.config import ENGINE
from examples.features.orm._0_create_tables import (
EmailAddress,
User,
)
# 0. Clean tables
with Session(ENGINE) as session:
session.execute(delete(EmailAddress))
session.execute(delete(User))
session.commit()
# 1. Insert multiple entries
data = [
{
"first_name": "Lux",
"last_name": "Noceda",
"email_addresses": ["fantasy4l!fe@hotmail.com", "lux.noceda@hexside.com"],
},
{
"first_name": "Eda",
"last_name": "Clawthorne",
"email_addresses": ["eda.clawthorne@owlhouse.com"],
},
{
"first_name": "Raine",
"last_name": "Whispers",
"email_addresses": ["bard.coven@community.com"],
},
{
"first_name": "Amity",
"last_name": "Blight",
"email_addresses": ["amity.blight@hexside.com"],
},
{
"first_name": "Willow",
"last_name": "Park",
"email_addresses": ["willow.park@hexside.com"],
},
]
with Session(ENGINE) as session:
# a. Add more entries using a dictionary
session.execute(insert(User), data)
# b. Send the pending changes to the session WITHOUT committing it yet;
# this updates Users with id values.
session.flush()
# c. Retrieve user ids
stmt = select(User.id, User.first_name, User.last_name)
user_map = {(u.first_name, u.last_name): u.id for u in session.execute(stmt)}
# d. Insert email for each user in the dictionary
email_payload = []
for entry in data:
user_id = user_map.get((entry["first_name"], entry["last_name"]))
for email in entry["email_addresses"]:
email_payload.append({"user_id": user_id, "email_address": email})
session.execute(insert(EmailAddress), email_payload)
# e. Commit all changes
session.commit()
# 2. Display the results
def select_all_entries():
with Session(ENGINE) as session:
stmt = select(User).options(joinedload(User.email_addresses)) # type: ignore
# .unique() is required for the 1-to-Many `joinedload` to deduplicate parent objects
results = session.scalars(stmt).unique().all()
for result in results:
emails = [e.email_address for e in result.email_addresses]
print(f"{result.id} {result.first_name} {result.last_name} {emails}")
select_all_entries()
# 3. Update multiple entries
with Session(ENGINE) as session:
# a. Fetch the users by their old last names
raine = session.query(User).filter_by(last_name="Whispers").first()
eda = session.query(User).filter_by(last_name="Clawthorne").first()
if raine and eda:
# b. Update Users last_name
raine.last_name = "Clawthorne-Whispers"
eda.last_name = "Clawthorne-Whispers"
# c. Update EmailAddress
eda_email = session.query(EmailAddress).filter_by(user_id=eda.id).first()
if eda_email:
eda_email.email_address = "eda.clawthorne-whispers@owlhouse.com" # type: ignore
session.commit()
print(f"\n--Users {eda.id} & {raine.id} have been updated.--")
select_all_entries()
# 4. Delete multiple entries
with Session(ENGINE) as session:
# a. Get the User.id for affected Users
targets = [("Amity", "Blight"), ("Willow", "Park"), ("Lux", "Noceda")]
stmt = select(User.id).filter( # type:ignore
tuple_(User.first_name, User.last_name).in_(targets)
)
user_ids = session.scalars(stmt).all()
# b. Delete EmailAddresses associated with these Users, as they graduated
for user_id in user_ids:
session.query(EmailAddress).filter(EmailAddress.user_id == user_id).delete(
synchronize_session=False
)
session.commit()
print(f"\n--EmailAddress for Users {user_ids} have been deleted.--")
select_all_entries()