SQLAlchemy supports ON CONFLICT with two methods on_conflict_do_update() and on_conflict_do_nothing().

Copying from the documentation:

from sqlalchemy.dialects.postgresql import insert

stmt = insert(my_table).values(user_email='[email protected]', data='inserted data')
stmt = stmt.on_conflict_do_update(
    index_elements=[my_table.c.user_email],
    index_where=my_table.c.user_email.like('%@gmail.com'),
    set_=dict(data=stmt.excluded.data)
)
conn.execute(stmt)
Answer from P.R. on Stack Overflow
Top answer
1 of 13
93

SQLAlchemy supports ON CONFLICT with two methods on_conflict_do_update() and on_conflict_do_nothing().

Copying from the documentation:

from sqlalchemy.dialects.postgresql import insert

stmt = insert(my_table).values(user_email='[email protected]', data='inserted data')
stmt = stmt.on_conflict_do_update(
    index_elements=[my_table.c.user_email],
    index_where=my_table.c.user_email.like('%@gmail.com'),
    set_=dict(data=stmt.excluded.data)
)
conn.execute(stmt)
2 of 13
81

SQLAlchemy does have a "save-or-update" behavior, which in recent versions has been built into session.add, but previously was the separate session.saveorupdate call. This is not an "upsert" but it may be good enough for your needs.

It is good that you are asking about a class with multiple unique keys; I believe this is precisely the reason there is no single correct way to do this. The primary key is also a unique key. If there were no unique constraints, only the primary key, it would be a simple enough problem: if nothing with the given ID exists, or if ID is None, create a new record; else update all other fields in the existing record with that primary key.

However, when there are additional unique constraints, there are logical issues with that simple approach. If you want to "upsert" an object, and the primary key of your object matches an existing record, but another unique column matches a different record, then what do you do? Similarly, if the primary key matches no existing record, but another unique column does match an existing record, then what? There may be a correct answer for your particular situation, but in general I would argue there is no single correct answer.

That would be the reason there is no built in "upsert" operation. The application must define what this means in each particular case.

🌐
Towards Data Science
towardsdatascience.com › home › latest › the easiest way to upsert with sqlalchemy
The easiest way to UPSERT with SQLAlchemy | Towards Data Science
January 21, 2025 - Our goal is to UPSERT the new data into the inventory table: ... We are going to use SQLAlchemy to UPSERT in 3 steps.
🌐
Medium
olegkhomenko.medium.com › how-to-create-an-sql-table-and-perform-upsert-using-sqlalchemy-orm-in-3-minutes-3f346ef7bdb5
How to create an SQL Table and perform UPSERT using SQLAlchemy ORM in 3 minutes | by Oleg Khomenko | Medium
April 27, 2023 - def upsert(users: dict, engine: sqlalchemy.engine.base.Engine, update=True): entries_to_update = 0 entries_to_put = [] with sessionmaker(bind=engine)() as sess: # Find all rows that needs to be updated and merge for each in ( sess.query(User.uuid) .filter(User.uuid.in_(users.keys())) .all() ): values = users.pop(each.uuid) entries_to_update += 1 if update: sess.merge(User(**values)) # Bulk mappings for everything that needs to be inserted for u in users.values(): entries_to_put.append(u) sess.bulk_insert_mappings(User, entries_to_put) sess.commit() return ( f" inserted:\t{len(entries_to_put)}\n" f" {'updated' if update else 'not updated'}:\t{str(entries_to_update)}" )
🌐
SQLAlchemy
docs.sqlalchemy.org › en › 20 › orm › queryguide › dml.html
ORM-Enabled INSERT, UPDATE, and DELETE statements — SQLAlchemy 2.0 Documentation
This mode of operation may be useful both for the case of passing SQL expressions on a per-row basis, and is also used when using “upsert” statements with the ORM, documented later in this chapter at ORM “upsert” Statements. A contrived example of an INSERT that embeds per-row SQL expressions, and also demonstrates Insert.returning() in this form, is below: >>> from sqlalchemy import select >>> address_result = session.scalars( ...
🌐
GitHub
github.com › sqlalchemy › sqlalchemy › discussions › 9328
Bulk upsert in Mysql dialect using on_duplicate_key_update · sqlalchemy/sqlalchemy · Discussion #9328
February 22, 2023 - Is it possible to perform bulk upsert? People have been asking on SO for some years: https://stackoverflow.com/questions/59291434/python-sqlalchemy-on-duplicate-key-update-with-multiple-records https://stackoverflow.com/questions/6611563/sqlalchemy-on-duplicate-key-update/48373874#48373874 · Thanks in advance for your expert guidance! Beta Was this translation helpful? Give feedback. ... Thanks for educating me here, it works! I also revised my example code to add setup and demonstrate upsert.
Author   sqlalchemy
🌐
GitHub
gist.github.com › bhtucker › c40578a2fb3ca50b324e42ef9dce58e1
A demonstration of Postgres upserts in SQLAlchemy · GitHub
A demonstration of Postgres upserts in SQLAlchemy. GitHub Gist: instantly share code, notes, and snippets.
🌐
Readthedocs
sqlalchemy-upsert-kit.readthedocs.io › en › latest › 01-Quick-Start-Guide › index.html
Quick Start Guide - sqlalchemy_upsert_kit 0.1.1 documentation
# Helper function to display data in table format import typing as T from sqlalchemy_upsert_kit.tests.utils import pt_from_many_dict def display_records(records: list[dict[str, T.Any]], title: str="Records"): print(f"\n{title}:") print(pt_from_many_dict(records)) # Set up timestamps create_time = datetime.now(timezone.utc).replace(microsecond=0) update_time = create_time + timedelta(minutes=1) # Prepare existing data (4 records with IDs 1-4) existing_data = [ {'id': 1, 'desc': 'v1', 'create_at': create_time, 'update_at': create_time}, {'id': 2, 'desc': 'v1', 'create_at': create_time, 'update_a
🌐
EDUCBA
educba.com › home › data science › data science tutorials › sql tutorial › sqlalchemy upsert
SQLAlchemy Upsert | What is sqlalchemy upsert? | How it Works?
June 8, 2023 - If we give a check on the contents of the table before and after upserting, we can get the following differences – · SQLAlchemy Upsert is the methodology where the new data is inserted in the table as well as existing data is modified and updated.
Address   Unit no. 202, Jay Antariksh Bldg, Makwana Road, Marol, Andheri (East),, 400059, Mumbai
Find elsewhere
🌐
GitHub
gist.github.com › malexer › 0647b208b2f1c48ec93e1bd157dc67c0
Modelling UPSERT in SQLAlchemy (well actually it is not upsert but speed improvement is significant in comparison with simple session.merge) · GitHub
Modelling UPSERT in SQLAlchemy (well actually it is not upsert but speed improvement is significant in comparison with simple session.merge) - sqlalchemy_upsert.py
🌐
Towards Data Science
towardsdatascience.com › home › latest › how to perform bulk insert/update/upsert actions with sqlalchemy orm
How to Perform Bulk Insert/Update/Upsert Actions with SQLAlchemy ORM | Towards Data Science
January 19, 2025 - This post covers different scenarios for bulk actions with SQLAlchemy ORM, including bulk inserts, updates, and upserts. Generally, insert action is much faster than updates and upserts. However, special attention should be paid when there are None values for the rows to be inserted.
🌐
GitHub
github.com › sqlalchemy › sqlalchemy › discussions › 11494
What's the best way to perform bulk upserts? · sqlalchemy/sqlalchemy · Discussion #11494
June 14, 2024 - so upsert capabilities depend on the db in use. In all case they require the use of statements. the session does not have that capability. For postgresql there is this https://docs.sqlalchemy.org/en/20/dialects/postgresql.html#sqlalchemy.dialects.postgresql.Insert.excluded that let you target the values that would be inserted for the update
Author   sqlalchemy
🌐
Medium
medium.com › data-science › the-easiest-way-to-upsert-with-sqlalchemy-9dae87a75c35
The easiest way to UPSERT with SQLAlchemy | by Mike Huls | TDS Archive | Medium
March 20, 2023 - The easiest way to UPSERT with SQLAlchemy One command to both INSERT new data and UPDATE existing records in your database In this short article we’ll find out how we can UPSERT in SQLAlchemy: we …
🌐
SQLAlchemy
docs.sqlalchemy.org › en › 20 › tutorial › orm_data_manipulation.html
Data Manipulation with the ORM — SQLAlchemy 2.0 Documentation
However, the ORM Session also has the ability to process commands that allow it to emit INSERT, UPDATE and DELETE statements directly without being passed any ORM-persisted objects, instead being passed lists of values to be INSERTed, UPDATEd, or upserted, or WHERE criteria so that an UPDATE ...
🌐
GitHub
github.com › sqlalchemy › sqlalchemy › discussions › 9702
Bulk upsert in SQLite dialect using INSERT…ON CONFLICT fails on Nullable column · sqlalchemy/sqlalchemy · Discussion #9702
Here is the revised SSCCE demonstrating bulk insert and bulk upsert in SQLite on a table that has nullable columns. import sqlalchemy as db import sqlalchemy.dialects.sqlite as sqlite from sqlalchemy import delete, select, String from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column class Base(DeclarativeBase): pass class User(Base): __tablename__ = 'user' id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(30), nullable=False) count: Mapped[int] = mapped_column(nullable=True) engine = db.create_engine('sqlite:///:memory:') conn = engine.connect() # setup step 0 - ensure the table exists Base().metadata.create_all(
Author   sqlalchemy
🌐
Angle of Attack
flightaware.engineering › making-sqlite-upserts-snappy-with-python-and-sqlalchemy
Making SQLite Upserts Snappy (with Python and SQLAlchemy)
January 21, 2022 - from sqlalchemy import event @event.listens_for(engine, "connect") def set_sqlite_pragma(dbapi_connection, connection_record): cursor = dbapi_connection.cursor() cursor.execute("PRAGMA synchronous=NORMAL") cursor.close()
🌐
Reddit
reddit.com › r/dataengineering › polars/sqlalchemy-> upsert data to database
r/dataengineering on Reddit: Polars/SQLAlchemy-> Upsert data to database
July 2, 2025 -

I'm currently learning Python, specifically the Polars API and the interaction with SQLAlchemy.

There are functions to read in and write data to a database (pl.read_databaae and pl.write_database). Now, I'm wondering if it's possible to further specify the import logic and if so, how would I do it? Specifically, I wan to perform an Upsert (insert or update) and as a table operation I want to define 'Create table if not exists'.

There is another function 'pl.write_delta', in which it's possible via multiple parameters to define the exact import logic to Delta Lake:

.when_matched_update_all() \
.when_not_matched_insert_all() \
.execute()

I assume it wasn't possible to generically include these parameters in write_database because all RDBMS handle Upsets differently? ...

So, what would be the recommended/best-practice way of upserting data to SQL Server? Can I do it with SQLAlchemy taking a Polars dataframe as an input?

The complete data pipeline looks like this:

  • read in flat file (xlsx/CSV/JSON) with Polars

  • perform some data wrangling operations with Polars

  • upsert data to SQL Server (with table operation 'Create table if not exists')

What I also found in a Stackoverflow post regarding Upserts with Polars:

df1 = (     df_new     .join(df_old, on = ["group","id"], how="inner")     .select(df_new.columns) )  df2 = (     df_new     .join(df_old, on = ["group","id"], how="anti") )  df3 = (     df_old     .join(df_new, on = ["group","id"], how="anti") )  df_all = pl.concat([df1, df2, df3])

Or with pl.update() I could perform an Upsert inside Polars:

df.update(new_df, left_on=["A"], right_on=["C"], how="full")

With both options though, I would have to read in the respective table from the database first, perform the Upsert with Polars and then write the output to the database again. This feels like 'overkill' to me?...

Anyways, thanks in advance for any help/suggestions!

Top answer
1 of 2
3
Generally you would upload to a staging table just to get the data into the database, then have the database itself handle upserts/merges. The database has to run the logic for the upsert anyway, so polars is just doing the upload portion. Breaking it into two distinct steps allows the database to first have a copy of the data that it has statistics about, and can do it as a single bulk operation. You also have the option of creating an index on the staging table before the merge, which could help that process. Breaking it up into two steps also means that once the data is uploaded, you don't need to run your local compute process anymore, and you aren't susceptible to the process failing if you have issues with your local processing, the network connection, or the merge. It is generally just more stable, and if it has an error you have a definite point the error occurred, and since the target table merge can be a single transaction, it is easy to rollback anything critical. Having your local data processing engine do a direct upload to a table lets it use whatever optimisations it has for the connection. If it has specific support for your database, it may make use of bulk operations in some way to speed up the process. Or, if you have a separate tool for bulk uploads, you write the data locally in a format suitable for that, push to a staging table with that tool, then merge with a database script or procedure. SQLAlchemy may be able to help with the database SQL script to integrate from the staging table to the target table.
2 of 2
1
You can find a list of community-submitted learning resources here: https://dataengineering.wiki/Learning+Resources I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.
🌐
Readthedocs
sqlalchemy-upsert-kit.readthedocs.io › en › latest › 02-Understanding-UPSERT-Operations › index.html
Understanding UPSERT Operations - sqlalchemy_upsert_kit 0.1.1 documentation
Simple, consistent API across all UPSERT patterns: import sqlalchemy_upsert_kit.api as sauk # Pattern 1: Skip duplicates ignored, inserted = sauk.sqlite.insert_or_ignore(engine, table, records) # Pattern 2: Replace duplicates updated, inserted = sauk.sqlite.insert_or_replace(engine, table, records) # Pattern 3: Selective merge (future implementation) updated, inserted = sauk.sqlite.insert_or_merge( engine, table, records, columns=['description', 'update_at'] ) Each database has its own optimized implementation: SQLite: sqlalchemy_upsert_kit.sqlite ·
🌐
Iditect
iditect.com › program-example › python--how-to-do-an-upsert-with-sqlalchemy.html
python - How to do an upsert with SqlAlchemy?
In SQLAlchemy, an "upsert" operation (insert or update) is typically achieved using the merge method or the insert method with the on_duplicate_key_update clause. The specific approach may depend on the database backend you are using. Here's an example using the merge method for a PostgreSQL ...
🌐
DNMTechs
dnmtechs.com › upsert-with-sqlalchemy-in-python-3
Upsert with SqlAlchemy in Python 3 – DNMTechs – Sharing and Storing Technology Knowledge
In this example, we create a table ... Finally, we commit the changes to the database. SqlAlchemy also provides the insert() and on_conflict_do_update() functions to perform an upsert operation....