SQLAlchemy ON DUPLICATE KEY UPDATE

时间:2011-07-07 13:43:08

标签: python mysql sqlalchemy

在SQLAlchemy中有一种优雅的方式来INSERT ... ON DUPLICATE KEY UPDATE吗?我的意思是语法类似于inserter.insert().execute(list_of_dictionaries)

9 个答案:

答案 0 :(得分:39)

ON DUPLICATE KEY UPDATE发布版本为1.2的MySQL

此功能现在内置于SQLAlchemy for MySQL中。 somada141的答案如下是最佳解决方案: https://stackoverflow.com/a/48373874/319066

SQL语句中的

ON DUPLICATE KEY UPDATE

如果您希望生成的SQL实际包含ON DUPLICATE KEY UPDATE,最简单的方法是使用@compiles装饰器。

可以找到代码(来自主题on reddit上的好主题的链接)以获取示例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)

但请注意,在这种方法中,您必须手动创建append_string。您可以更改append_string函数,以便它自动将插入字符串更改为带有“ON DUPLICATE KEY UPDATE”字符串的插入,但由于懒惰,我不打算这样做。

ORM 中的

ON DUPLICATE KEY UPDATE功能

SQLAlchemy不提供ON DUPLICATE KEY UPDATEMERGE的接口或其ORM层中的任何其他类似功能。尽管如此,它还具有session.merge()功能,只有当相关密钥是主键时才能复制功能

session.merge(ModelObject)首先通过发送SELECT查询(或在本地查找)来检查是否存在具有相同主键值的行。如果是,它会在某处设置一个标志,指示ModelObject已经在数据库中,并且SQLAlchemy应该使用UPDATE查询。请注意,merge比这复杂得多,但它可以很好地复制主键。

但是,如果您希望ON DUPLICATE KEY UPDATE功能与非主键(例如,另一个唯一键),该怎么办?不幸的是,SQLAlchemy没有任何这样的功能。相反,你必须创建类似于Django get_or_create()的东西。 Another StackOverflow answer covers it,为了方便起见,我将在此处粘贴修改后的工作版本。

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

答案 1 :(得分:7)

我应该提一下,自v1.2版本发布以来,SQLAlchemy'核心'已经内置了上面的解决方案,可以在here下看到(下面复制的代码片段):

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)

答案 2 :(得分:1)

有一个更简单的解决方案:

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

@compiles(Insert)
def replace_string(insert, compiler, **kw):
    s = compiler.visit_insert(insert, **kw)
    s = s.replace("INSERT INTO", "REPLACE INTO")
    return s

my_connection.execute(my_table.insert(replace_string=""), my_values)

答案 3 :(得分:1)

这取决于你。如果要替换,请在前缀中传递OR REPLACE

  def bulk_insert(self,objects,table):
    #table: Your table class and objects are list of dictionary [{col1:val1, col2:vale}] 
    for counter,row in enumerate(objects):
        inserter = table.__table__.insert(prefixes=['OR IGNORE'], values=row)
        try:
            self.db.execute(inserter)
        except Exception as E:
            print E
        if counter % 100 == 0:
            self.db.commit()                    
    self.db.commit()

此处提交间隔可以更改为加速或减速

答案 4 :(得分:1)

基于

phsource's answer,以及使用的特定的用例的 MySQL的并完全覆盖了相同的密钥数据,而不执行DELETE语句,可以使用以下@compiles修饰的插入表达式:

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 insert.kwargs.get('on_duplicate_key_update'):
        fields = s[s.find("(") + 1:s.find(")")].replace(" ", "").split(",")
        generated_directive = ["{0}=VALUES({0})".format(field) for field in fields]
        return s + " ON DUPLICATE KEY UPDATE " + ",".join(generated_directive)
    return s

答案 5 :(得分:0)

我只使用普通的sql:

insert_stmt = "REPLACE INTO tablename (column1, column2) VALUES (:column_1_bind, :columnn_2_bind) "
session.execute(insert_stmt, data)

答案 6 :(得分:0)

我的方式

import typing
from datetime import datetime
from sqlalchemy.dialects import mysql

class MyRepository:

    def model(self):
        return MySqlAlchemyModel

    def upsert(self, data: typing.List[typing.Dict]):
        if not data:
            return
        model = self.model()
        if hasattr(model, 'created_at'):
            for item in data:
                item['created_at'] = datetime.now()

        stmt = mysql.insert(getattr(model, '__table__')).values(data)
        for_update = []
        for k, v in data[0].items():
            for_update.append(k)

        dup = {k: getattr(stmt.inserted, k) for k in for_update}
        stmt = stmt.on_duplicate_key_update(**dup)
        self.db.session.execute(stmt)
        self.db.session.commit()

用法:

myrepo.upsert([
    {
        "field11": "value11",
        "field21": "value21",
        "field31": "value31",
    },
    {
        "field12": "value12",
        "field22": "value22",
        "field32": "value32",
    },
])

答案 7 :(得分:0)

其他答案都涵盖了这个问题,但是我想参考另一个我在this要点中找到的mysql的好例子。这还包括使用LAST_INSERT_ID,这取决于您的innodb自动增量设置以及表是否具有唯一键,这可能会很有用。在此处举起代码以便于参考,但如果发现有用,请给作者加星号。

from app import db
from sqlalchemy import func
from sqlalchemy.dialects.mysql import insert

def upsert(model, insert_dict):
    """model can be a db.Model or a table(), insert_dict should contain a primary or unique key."""
    inserted = insert(model).values(**insert_dict)
    upserted = inserted.on_duplicate_key_update(
        id=func.LAST_INSERT_ID(model.id), **{k: inserted.inserted[k]
                               for k, v in insert_dict.items()})
    res = db.engine.execute(upserted)
    return res.lastrowid

答案 8 :(得分:-1)

因为这些解决方案都不是那么优雅。一种强制方式是查询该行是否存在。如果确实删除了行,则插入否则只插入。显然有些开销涉及但它不依赖于修改原始sql而且它适用于非orm的东西。