ON DUPLICATE KEY UPDATE post version-1.2 for MySQL

This functionality is now built into SQLAlchemy for MySQL only. somada141's answer below has the best solution: https://stackoverflow.com/a/48373874/319066

ON DUPLICATE KEY UPDATE in the SQL statement

If you want the generated SQL to actually include ON DUPLICATE KEY UPDATE, the simplest way involves using a @compiles decorator.

The code (linked from a good thread on the subject on reddit) for an example can be found on github:

from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import Insert

@compiles(Insert)
def append_string(insert, compiler, **kw):
    s = compiler.visit_insert(insert, **kw)
    if 'append_string' in insert.kwargs:
        return s + " " + insert.kwargs['append_string']
    return s


my_connection.execute(my_table.insert(append_string = 'ON DUPLICATE KEY UPDATE foo=foo'), my_values)

But note that in this approach, you have to manually create the append_string. You could probably change the append_string function so that it automatically changes the insert string into an insert with 'ON DUPLICATE KEY UPDATE' string, but I'm not going to do that here due to laziness.

ON DUPLICATE KEY UPDATE functionality within the ORM

SQLAlchemy does not provide an interface to ON DUPLICATE KEY UPDATE or MERGE or any other similar functionality in its ORM layer. Nevertheless, it has the session.merge() function that can replicate the functionality only if the key in question is a primary key.

session.merge(ModelObject) first checks if a row with the same primary key value exists by sending a SELECT query (or by looking it up locally). If it does, it sets a flag somewhere indicating that ModelObject is in the database already, and that SQLAlchemy should use an UPDATE query. Note that merge is quite a bit more complicated than this, but it replicates the functionality well with primary keys.

But what if you want ON DUPLICATE KEY UPDATE functionality with a non-primary key (for example, another unique key)? Unfortunately, SQLAlchemy doesn't have any such function. Instead, you have to create something that resembles Django's get_or_create(). Another StackOverflow answer covers it, and I'll just paste a modified, working version of it here for convenience.

def get_or_create(session, model, defaults=None, **kwargs):
    instance = session.query(model).filter_by(**kwargs).first()
    if instance:
        return instance
    else:
        params = dict((k, v) for k, v in kwargs.iteritems() if not isinstance(v, ClauseElement))
        if defaults:
            params.update(defaults)
        instance = model(**params)
        return instance
Answer from phsource on Stack Overflow
Top answer
1 of 11
59

ON DUPLICATE KEY UPDATE post version-1.2 for MySQL

This functionality is now built into SQLAlchemy for MySQL only. somada141's answer below has the best solution: https://stackoverflow.com/a/48373874/319066

ON DUPLICATE KEY UPDATE in the SQL statement

If you want the generated SQL to actually include ON DUPLICATE KEY UPDATE, the simplest way involves using a @compiles decorator.

The code (linked from a good thread on the subject on reddit) for an example can be found on github:

from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.expression import Insert

@compiles(Insert)
def append_string(insert, compiler, **kw):
    s = compiler.visit_insert(insert, **kw)
    if 'append_string' in insert.kwargs:
        return s + " " + insert.kwargs['append_string']
    return s


my_connection.execute(my_table.insert(append_string = 'ON DUPLICATE KEY UPDATE foo=foo'), my_values)

But note that in this approach, you have to manually create the append_string. You could probably change the append_string function so that it automatically changes the insert string into an insert with 'ON DUPLICATE KEY UPDATE' string, but I'm not going to do that here due to laziness.

ON DUPLICATE KEY UPDATE functionality within the ORM

SQLAlchemy does not provide an interface to ON DUPLICATE KEY UPDATE or MERGE or any other similar functionality in its ORM layer. Nevertheless, it has the session.merge() function that can replicate the functionality only if the key in question is a primary key.

session.merge(ModelObject) first checks if a row with the same primary key value exists by sending a SELECT query (or by looking it up locally). If it does, it sets a flag somewhere indicating that ModelObject is in the database already, and that SQLAlchemy should use an UPDATE query. Note that merge is quite a bit more complicated than this, but it replicates the functionality well with primary keys.

But what if you want ON DUPLICATE KEY UPDATE functionality with a non-primary key (for example, another unique key)? Unfortunately, SQLAlchemy doesn't have any such function. Instead, you have to create something that resembles Django's get_or_create(). Another StackOverflow answer covers it, and I'll just paste a modified, working version of it here for convenience.

def get_or_create(session, model, defaults=None, **kwargs):
    instance = session.query(model).filter_by(**kwargs).first()
    if instance:
        return instance
    else:
        params = dict((k, v) for k, v in kwargs.iteritems() if not isinstance(v, ClauseElement))
        if defaults:
            params.update(defaults)
        instance = model(**params)
        return instance
2 of 11
40

I should mention that ever since the v1.2 release, the SQLAlchemy 'core' has a solution to the above with that's built in and can be seen under here (copied snippet below):

from sqlalchemy.dialects.mysql import insert

insert_stmt = insert(my_table).values(
    id='some_existing_id',
    data='inserted value')

on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update(
    data=insert_stmt.inserted.data,
    status='U'
)

conn.execute(on_duplicate_key_stmt)
🌐
GitHub
github.com › sqlalchemy › sqlalchemy › discussions › 9328
Bulk upsert in Mysql dialect using on_duplicate_key_update · sqlalchemy/sqlalchemy · Discussion #9328
February 22, 2023 - Please note I'm using SQLAlchemy version 2.0.4. Unfortunately I do not see the desired upsert behavior. ... INSERT INTO foo (name) VALUES (%s), (%s), (%s), (%s) ON DUPLICATE KEY UPDATE name = VALUES(name) {'name_m0': 'x', 'name_m1': 'y', 'name_m2': 'w', 'name_m3': 'z'} And when I query the database, I see 4 new rows ...
Author   sqlalchemy
Discussions

SqlAlchemy: What's the idiomatic way of writing INSERT... ON DUPLICATE KEY UPDATE

Adding SQL constructs can be done with custom ClauseElements. Someone already implemented ON DUPLICATE KEY in sqlalchemy_mysql_ext.

More on reddit.com
🌐 r/Python
13
8
February 1, 2012
Duplicate Key error, need Update instead of Insert
I am trying to add a Host to an existing Device. Previous to adding the Host I am checking the OS exists on the db to avoid a duplicate entry error. The problem I have is that if the OS already exists on the db sqlalchemy will try to insert it and thus generating a duplicate entry error. More on github.com
🌐 github.com
2
2
python - INSERT or UPDATE ON DUPLICATE KEY with SQLAlchemy ORM - Stack Overflow
I'm looking for a function in MYSQLAlchemy's ORM to make a statement for all rows in dataset to load in DDBB at once with insert or update un duplicate key, but only found to execute row by row. Ac... More on stackoverflow.com
🌐 stackoverflow.com
python - SqlAlchemy ON DUPLICATE KEY UPDATE for bulk upsert - Stack Overflow
I'm confused about the syntax for SqlAlchemy's ON DUPLICATE KEY UPDATE. I've tried to adapt the example in link below, also studying the documentation for insert specifically. https://docs.sqlalche... More on stackoverflow.com
🌐 stackoverflow.com
Top answer
1 of 4
11

I just ran into a similar problem and creating a dictionary out of query.inserted solved it for me.

query = insert(table).values(record_list)
update_dict = {x.name: x for x in query.inserted}
upsert_query = query.on_duplicate_key_update(update_dict)
2 of 4
3

Thanks to Federico Caselli of the SQLAlchemy project for explaining how to use on_duplicate_key_update in a discussion https://github.com/sqlalchemy/sqlalchemy/discussions/9328

Here's a Python3 script that demonstrates how to use SQLAlchemy version 2 to implement upsert using on_duplicate_key_update in the MySQL dialect:

import sqlalchemy as db
import sqlalchemy.dialects.mysql as mysql
from sqlalchemy import delete, select, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "foo"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(30))


engine = db.create_engine('mysql+mysqlconnector://USER-NAME-HERE:PASS-WORD-HERE@localhost/SCHEMA-NAME-HERE')
conn = engine.connect()

# setup step 0 - ensure the table exists
Base().metadata.create_all(bind=engine)

# setup step 1 - clean out rows with id 1..5
del_stmt = delete(User).where(User.id.in_([1, 2, 3, 4, 5]))
conn.execute(del_stmt)
conn.commit()
sel_stmt = select(User)
users = list(conn.execute(sel_stmt))
print(f'Table size after cleanout: {len(users)}')

# setup step 2 - insert 4 rows
ins_stmt = mysql.insert(User).values(
    [
        {"id": 1, "name": "x"},
        {"id": 2, "name": "y"},
        {"id": 3, "name": "w"},
        {"id": 4, "name": "z"},
    ]
)
conn.execute(ins_stmt)
conn.commit()
users = list(conn.execute(sel_stmt))
print(f'Table size after insert: {len(users)}')

# demonstrate upsert
ups_stmt = mysql.insert(User).values(
    [
        {"id": 1, "name": "xx"},
        {"id": 2, "name": "yy"},
        {"id": 3, "name": "ww"},
        {"id": 5, "name": "new"},
    ]
)
ups_stmt = ups_stmt.on_duplicate_key_update(name=ups_stmt.inserted.name)
# if you want to see the compiled result
# x = ups_stmt.compile(dialect=mysql.dialect())
# print(x.string, x.construct_params())
conn.execute(ups_stmt)
conn.commit()

users = list(conn.execute(sel_stmt))
print(f'Table size after upsert: {len(users)}')
🌐
SQLAlchemy
docs.sqlalchemy.org › en › 14 › changelog › migration_12.html
What’s New in SQLAlchemy 1.2? — SQLAlchemy 1.4 Documentation
October 16, 2022 - INSERT INTO my_table (id, data) VALUES (:id, :data) ON DUPLICATE KEY UPDATE data=VALUES(data), status=:status_1
🌐
GitHub
gist.github.com › timtadh › 7811458
How to compile an INSERT ... ON DUPLICATE KEY UPDATE with SQL Alchemy with support for a bulk insert. · GitHub
How to compile an INSERT ... ON DUPLICATE KEY UPDATE with SQL Alchemy with support for a bulk insert. - upsert.py
🌐
SQLAlchemy
docs.sqlalchemy.org › en › 20 › orm › queryguide › dml.html
ORM-Enabled INSERT, UPDATE, and DELETE statements — SQLAlchemy 2.0 Documentation
The ORM-enabled UPDATE and DELETE features bypass ORM unit of work automation in favor of being able to emit a single UPDATE or DELETE statement that matches multiple rows at once without complexity. The operations do not offer in-Python cascading of relationships - it is assumed that ON UPDATE CASCADE and/or ON DELETE CASCADE is configured for any foreign key references which require it, otherwise the database may emit an integrity violation if foreign key references are being enforced.
🌐
SQLAlchemy
docs.sqlalchemy.org › en › 21 › changelog › migration_12.html
What’s New in SQLAlchemy 1.2? — SQLAlchemy 2.1 Documentation
INSERT INTO my_table (id, data) VALUES (:id, :data) ON DUPLICATE KEY UPDATE data=VALUES(data), status=:status_1
🌐
Hackthology
hackthology.com › how-to-compile-mysqls-on-duplicate-key-update-in-sql-alchemy.html
How to Compile MySQL's "ON DUPLICATE KEY UPDATE" in SQL Alchemy
This snippet contains a well tested way to compile an "Upsert" statement for MySQL in SQL Alchemy. I have found it useful for several project and I am making it available via this gist for others to enjoy and improve. Unlike some other examples out there this one supports doing a bulk insert ...
Find elsewhere
🌐
Google Groups
groups.google.com › g › sqlalchemy › c › LVz9Visz8Ys
INSERT ... ON DUPLICATE KEY UPDATE
Run a database select to see which of those keys are present in the database, and then divide your batch into two parts: data needing insert and data needing update. If you've got write contention for this data you'd need to work more granularly (likely row by row) instead, keeping in mind the database engine's transaction model and ideally taking advantage of any tools the db engine provides (like ON DUPLICATE or sql's MERGE) .
🌐
Reddit
reddit.com › r/python › sqlalchemy: what's the idiomatic way of writing insert... on duplicate key update
r/Python on Reddit: SqlAlchemy: What's the idiomatic way of writing INSERT... ON DUPLICATE KEY UPDATE
February 1, 2012 -

on SqlAlchemy core (not using Session/ORM)?

I have quite a few tables with composite primary keys that can use it for optimization.

At the moment what I have is a bit ghetto:

stmt = str(user_table.insert().values(email=email, name=name))
stmt += " ON DUPLICATE KEY UPDATE name=%s"
engine.execute(stmt, email, name, name)

NOTE: I believe this is not standard SQL. It's MySQL specific.

🌐
Stack Overflow
stackoverflow.com › questions › 74208312 › insert-or-update-on-duplicate-key-with-sqlalchemy-orm
python - INSERT or UPDATE ON DUPLICATE KEY with SQLAlchemy ORM - Stack Overflow
I'm looking for a function in MYSQLAlchemy's ORM to make a statement for all rows in dataset to load in DDBB at once with insert or update un duplicate key, but only found to execute row by row. Ac...
🌐
DNMTechs
dnmtechs.com › sqlalchemy-on-duplicate-key-update-in-python-3
SQLAlchemy On Duplicate Key Update in Python 3 – DNMTechs – Sharing and Storing Technology Knowledge
In this example, we first create a SQLAlchemy engine and connect to a MySQL database. We define a table with columns ‘id’, ‘name’, and ‘value’. We then create an insert statement for the table with values for ‘id’, ‘name’, and ‘value’. We use the ‘on_duplicate_key_update’ method to specify that if a duplicate key is found, the ‘value’ column should be updated to 100.
🌐
Stack Overflow
stackoverflow.com › questions › 63393069 › sqlalchemy-on-duplicate-key-update-for-bulk-upsert
python - SqlAlchemy ON DUPLICATE KEY UPDATE for bulk upsert - Stack Overflow
https://docs.sqlalchemy.org/en/13/dialects/mysql.html#insert-on-duplicate-key-update-upsert ... class GivenName(Base): # ORM table definition __tablename__ = 'given_name' given_company_name = Column(String(255), index=True, primary_key=True) given_company_name_trimmed = Column(String(255), index=True) given_org_number = Column(String(255)) given_clean_org_number = Column(BigInteger, index=True, primary_key=True) # two rows to be inserted in json format insertion_as_dict = [{'given_clean_org_number': 0.0, 'given_company_name': 'staby gårdshotell', 'given_company_name_trimmed': 'staby gardshote
🌐
tutorialpedia
tutorialpedia.org › blog › sqlalchemy-core-insert-ignore-and-on-duplicate-key-update
SQLAlchemy Core: How to Use INSERT IGNORE and ON DUPLICATE KEY UPDATE in MySQL (Avoid Manual Queries) — tutorialpedia.org
ON DUPLICATE KEY UPDATE updates existing rows when a duplicate is detected. Use SQLAlchemy’s on_duplicate_key_update() method to define columns to update.
🌐
DevPress
devpress.csdn.net › mysqldb › 63023b40c677032930808de9.html
SQLAlchemy ON DUPLICATE KEY UPDATE_python_weixin_0010034-MySQL数据库
August 21, 2022 - SQLAlchemy does not provide an interface to ON DUPLICATE KEY UPDATE or MERGE or any other similar functionality in its ORM layer. Nevertheless, it has the session.merge() function that can replicate the functionality only if the key in question is a primary key. session.merge(ModelObject) first ...
🌐
Google Groups
groups.google.com › g › sqlalchemy › c › KJEUr_7cG6o
many-to-many relationship : how to update items properly if there are duplicates entries ?
May 22, 2017 - > ); > | > > > The problem is that when adding tags that already exists in the `tag` table > > | > post.tags ='a'# tag 'a' already created > | > > it produces this exception : sqlalchemy.orm.exc.FlushError: New instance > <Tag at 0x7f7b56a3a940> with identity key (<class 'test_mm.Tag'>, > ('a',)) conflicts with persistent instance <Tag at 0x7f7b567af7f0> > > > Ideally, I'd like to be able to produce a query like (MySQL) : > > | > INSERT INTO tag (`key`,`count`)VALUES (%s,1)ON DUPLICATE KEY UPDATE > count =count +1 > | > > Instead that, the way I found to do seems much less efficient : > > | > @tags.setter > deftags(self,s): > sess =object_session(self) > lst =[sess.merge(Tag(key=tag))fortag ins.split(';')] > self._tags =lst > | > > (and the counter is managed by a trigger on `post_tags` INSERT) > > Is there another way to do that properly and efficiently ?