SQLAlchemy Upsert: Update if Exists, Insert if Not

Updated: January 3, 2024 By: Guest Contributor Post a comment

Introduction

When working with databases, a common task is to either insert a new record or update an existing one. This tutorial explores how to execute an ‘upsert’ operation in SQLAlchemy, ensuring an efficient way to handle record creation or modification.

Understanding Upsert in SQLAlchemy

An upsert is a database operation that inserts rows into a database table if they do not already exist, or updates them if they do. SQLAlchemy, a popular SQL toolkit and Object-Relational Mapping (ORM) library for Python, provides several ways to accomplish this task.
First, let’s get our environment set up.

# Install SQLAlchemy
pip install sqlalchemy

Import the necessary modules from SQLAlchemy:

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Basics of Upserting

Let’s start by creating a simple model that represents our database table:

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    email = Column(String)

We then establish a connection to our database:

engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine))
Session = sessionmaker(bind=engine)
session = Session()

Naive Upsert Approach

One might attempt a naive upsert by checking if a record exists first and then deciding to either update or insert.

instance = session.query(User).filter_by(email='[email protected]').first()
if instance:
    # Update the record
    instance.name = 'New Name'
    session.commit()
else:
    # Insert the record
    new_user = User(name='New Name', email='[email protected]')
    session.add(new_user)
    session.commit()

Using SQL Expressions for Upserts

SQLAlchemy core provides a more efficient upsert mechanism using SQL expression language:

from sqlalchemy.dialects.postgresql import insert

insert_stmt = insert(User).values(name='New Name', email='[email protected]')
upsert_stmt = insert_stmt.on_conflict_do_update(
    index_elements=['email'],
    set_={'name': insert_stmt.excluded.name}
)
session.execute(upsert_stmt)
session.commit()

This approach effectively combines the insert and update operations into a single atomic action that is more efficient and reliable.

Advanced Usage of Upserts with Custom Conflict Actions

SQLAlchemy allows for even more control over upserts with custom conflict actions:

# Additional imports for advanced upsert
from sqlalchemy.dialects.postgresql import excludable as exc

upsert_stmt = insert(User).
    values(name='John Doe', email='[email protected]').
    on_conflict_do_update(
        constraint='users_email_key',
        set_={'name': exc.name}
    )
session.execute(upsert_stmt)
session.commit()

In this example, we use a predefined constraint (‘users_email_key’) to handle conflicts more precisely, allowing for a tailored upsert operation.

Conclusion

SQLAlchemy provides a versatile toolkit for managing database operations, including upserts. With upserts, you can maintain database integrity and improve performance by avoiding separate insert and update transactions. Whether you are working on a small project or a large-scale application, mastering upserts in SQLAlchemy can lead to more efficient and robust database handling.