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 - 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'} Beta Was this translation helpful? Give feedback. ... There was an error while loading. Please reload this page. Something went wrong. There was an error while loading. Please reload this page. ... Thank you very much for the code! I extended it slightly to connect a local MySQL server running version 8.0.27. Please note I'm using SQLAlchemy version 2.0.4.
Author   sqlalchemy
Discussions

Duplicate Key error, need Update instead of Insert
Yes, we activated the logs, and we can confirm that slq-alchemy is executing an INSERT on the os table. We need to stop that from happening when the os has been found. Here are the logs: 2023-02-07 12:05:15,047 INFO sqlalchemy.engine.Engine SELECT operating_system.id AS operating_system_id, ... More on github.com
🌐 github.com
2
2
mysql - Python SQLAlchemy ON DUPLICATE KEY UPDATE with multiple records - Stack Overflow
I'd like to use the ON DUPLICATE KEY UPDATE optionality provided by SQLAlchemy to upsert a bunch of records. These records have been sucessfully inserted with python using the following (where More on stackoverflow.com
🌐 stackoverflow.com
python - SQLAlchemy Core - INSERT IGNORE and ON DUPLICATE KEY UPDATE - Stack Overflow
I'm using SQLAlchemy Core with a MySQL database but am having a hard time finding a solution for INSERT IGNORE / DUPLICATE KEY UPDATE. I hate to write a one-off query manually in the code if there'... More on stackoverflow.com
🌐 stackoverflow.com
python - Getting SQLAlchemy to do "ON DUPLICATE KEY UPDATE" inside an ORM cascade (in MySQL) - Stack Overflow
I'm cross posting this from the python reddit channel where i haven't managed to get a response on this yet. I'm surprised as this seems like an extremely standard scenario. My question is whether... More on stackoverflow.com
🌐 stackoverflow.com
November 24, 2015
🌐
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.

🌐
SQLAlchemy
docs.sqlalchemy.org › en › 21 › changelog › migration_12.html
What’s New in SQLAlchemy 1.2? — SQLAlchemy 2.1 Documentation
This Insert subclass adds a new method Insert.on_duplicate_key_update() that implements MySQL’s syntax:
🌐
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
class Upsert(sqlalchemy.sql.expression.Insert): pass from sqlalchemy.ext.compiler import compiles @compiles(Upsert, "mysql") def compile_upsert(insert_stmt, compiler, **kwargs): if insert_stmt._has_multi_parameters: keys = insert_stmt.parameters[0].keys() else: keys = insert_stmt.parameters.keys() pk = insert_stmt.table.primary_key auto = None if (len(pk.columns) == 1 and isinstance(pk.columns.values()[0].type, sqlalchemy.Integer) and pk.columns.values()[0].autoincrement): auto = pk.columns.keys()[0] if auto in keys: keys.remove(auto) insert = compiler.visit_insert(insert_stmt, **kwargs) ondup
🌐
SQLAlchemy
docs.sqlalchemy.org › en › 14 › changelog › migration_12.html
What’s New in SQLAlchemy 1.2? — SQLAlchemy 1.4 Documentation
June 24, 2022 - This Insert subclass adds a new method Insert.on_duplicate_key_update() that implements MySQL’s syntax:
🌐
SQLAlchemy
docs.sqlalchemy.org › en › 13 › dialects › mysql.html
MySQL — SQLAlchemy 1.3 Documentation
ON DUPLICATE KEY UPDATE is used to perform an update of the already existing row, using any combination of new values as well as values from the proposed insertion.
Find elsewhere
🌐
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 ...
🌐
Google Groups
groups.google.com › g › sqlalchemy › c › LVz9Visz8Ys
INSERT ... ON DUPLICATE KEY UPDATE
if i would use plain (literal) sql, i could execute insert in $SUBJ. but i would like to use sqlalchemy's native solution if it's possible. however, i don't know how to do this. can anyone help me? thanks c. ... Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message ... What about something like this: # get by primary key, you could also use a where clause item = session.query(db.YourClass).get(131312) if item: # do whatever to update print item else: # create a new one newItem = db.YourClass() print newItem session.add(newItem) # commit session.commit() > thanks > > c.
🌐
SQLAlchemy
docs.sqlalchemy.org › en › 21 › dialects › mysql.html
MySQL and MariaDB — SQLAlchemy 2.1 Documentation
INSERT INTO my_table (id, data) VALUES (%s, %s) ON DUPLICATE KEY UPDATE data = %s, updated_at = CURRENT_TIMESTAMP · In a manner similar to that of UpdateBase.values(), other parameter forms are accepted, including a single dictionary: >>> on_duplicate_key_stmt = insert_stmt.on_duplicate_key_update( ...
🌐
SQLAlchemy
docs.sqlalchemy.org › en › 20 › changelog › migration_12.html
What’s New in SQLAlchemy 1.2? — SQLAlchemy 2.0 Documentation
April 30, 2023 - This Insert subclass adds a new method Insert.on_duplicate_key_update() that implements MySQL’s syntax:
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)}')
🌐
GitHub
github.com › sqlalchemy › sqlalchemy › issues › 4009
implement MySQL ON DUPLICATE KEY UPDATE · Issue #4009 · sqlalchemy/sqlalchemy
Added support for MySQL's ON DUPLICATE KEY UPDATE MySQL-specific :class:.mysql.dml.Insert object. Pull request courtesy Michael Doronin. Co-authored-by: Mike Bayer mike_mp@zzzcomputing.com Resolves: #4009 Change-Id: Ic71424f3c88af6082b48a910a2efb7fbfc0a7eb4 Pull-request: zzzeek/sqlalchemy#365
Top answer
1 of 1
6

AFAIK SQLAlchemy ORM layer doesn't have a way to do an ON DUPLICATE KEY UPDATE. Fortunately the session.merge() function can do this for you, but only if the key is a primary key (your case is).

session.merge(o) checks if a row with the same primary key value exists by issuing a SELECT and, if true, it issues an UPDATE instead of INSERT.

See this example:

from sqlalchemy import create_engine, Column, types
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session

engine = create_engine('sqlite:///:memory:', echo=False)
Base = declarative_base()
session = scoped_session(sessionmaker(bind=engine))

class User(Base):
    __tablename__ = "user"
    login = Column(types.String(50), primary_key=True)
    name = Column(types.String(255))

    def __repr__(self):
        return "User(login=%r, name=%r)" % (self.login, self.name)

Base.metadata.create_all(engine)

if __name__ == '__main__':
    # create two users    
    u1 = User(login='iuridiniz', name="Iuri Diniz")
    u2 = User(login='someuser', name="Some User")
    session.merge(u1) # could be session.add(u1)
    session.merge(u2) # could be session.add(u2)
    session.commit()

    # print all users
    print("First two users")
    for u in session.query(User):
        print(u)

    # create more two users, one with the same login
    u3 = User(login='iuridiniz', name="Iuri Gomes Diniz")
    u4 = User(login='anotheruser', name="Another User")
    session.merge(u3) # session.add(u3) will raise a sqlalchemy.exc.IntegrityError
    session.merge(u4) # could be session.add(u4)
    session.commit()

    print("More two users")
    for u in session.query(User):
        print(u)

The output:

First two users
User(login=u'iuridiniz', name=u'Iuri Diniz')
User(login=u'someuser', name=u'Some User')
More two users
User(login=u'iuridiniz', name=u'Iuri Gomes Diniz')
User(login=u'someuser', name=u'Some User')
User(login=u'anotheruser', name=u'Another User')

Change engine = create_engine('sqlite:///:memory:', echo=False) to engine = create_engine('sqlite:///:memory:', echo=True) in order to view the queries executed:

[INFO Engine] SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1
[INFO Engine] ()
[INFO Engine] SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1
[INFO Engine] ()
[INFO Engine] PRAGMA table_info("user")
[INFO Engine] ()
[INFO Engine] 
CREATE TABLE user (
    login VARCHAR(50) NOT NULL, 
    name VARCHAR(255), 
    PRIMARY KEY (login)
)


[INFO Engine] ()
[INFO Engine] COMMIT
[INFO Engine] BEGIN (implicit)
[INFO Engine] SELECT user.login AS user_login, user.name AS user_name 
FROM user 
WHERE user.login = ?
[INFO Engine] ('iuridiniz',)
[INFO Engine] INSERT INTO user (login, name) VALUES (?, ?)
[INFO Engine] ('iuridiniz', 'Iuri Diniz')
[INFO Engine] SELECT user.login AS user_login, user.name AS user_name 
FROM user 
WHERE user.login = ?
[INFO Engine] ('someuser',)
[INFO Engine] INSERT INTO user (login, name) VALUES (?, ?)
[INFO Engine] ('someuser', 'Some User')
[INFO Engine] COMMIT
[INFO Engine] BEGIN (implicit)
[INFO Engine] SELECT user.login AS user_login, user.name AS user_name 
FROM user 
WHERE user.login = ?
[INFO Engine] ('iuridiniz',)
[INFO Engine] UPDATE user SET name=? WHERE user.login = ?
[INFO Engine] ('Iuri Gomes Diniz', 'iuridiniz')
[INFO Engine] SELECT user.login AS user_login, user.name AS user_name 
FROM user 
WHERE user.login = ?
[INFO Engine] ('anotheruser',)
[INFO Engine] INSERT INTO user (login, name) VALUES (?, ?)
[INFO Engine] ('anotheruser', 'Another User')
[INFO Engine] COMMIT
🌐
GitHub
github.com › sqlalchemy › sqlalchemy › issues › 8626
mysql 8.0.19 and above supports aliases for ON DUPLICATE KEY UPDATE, VALUES() in replaced row warns · Issue #8626 · sqlalchemy/sqlalchemy
October 12, 2022 - Please use an alias (INSERT INTO ... VALUES (...) AS alias) and replace VALUES(col) in the ON DUPLICATE KEY UPDATE clause with alias.col instead · OS: Python: SQLAlchemy: Database: DBAPI (eg: psycopg, cx_oracle, mysqlclient): No response · Reactions are currently unavailable ·
Author   btilly
🌐
SQLAlchemy
docs.sqlalchemy.org › en › 20 › search.html
Search — SQLAlchemy 2.0 Documentation
Contents | Index | Download this Documentation · Home | Download this Documentation