在SQLAlchemy中有一种优雅的方式来INSERT ... ON DUPLICATE KEY UPDATE
吗?我的意思是语法类似于inserter.insert().execute(list_of_dictionaries)
?
答案 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 UPDATE
或MERGE
的接口或其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的东西。