SQLAlchemy ON DUPLICATE KEY UPDATE
有没有一种优雅的方法可以在SQLAlchemy中执行
现在仅在MySQL的SQLAlchemy中内置了此功能。 somada141的以下答案是最好的解决方案:
https://stackoverflow.com/a/48373874/319066
SQL语句中的
如果希望生成的SQL实际上包含
可以在github上找到示例代码(从reddit上的主题上的一个好的线程链接)。
1 2 3 4 5 6 7 8 9 10 11 12 | 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中的
SQLAlchemy在其ORM层中未提供
但是,如果您想要具有非主键(例如,另一个唯一键)的
1 2 3 4 5 6 7 8 9 10 | 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 |
我要提到的是,自v1.2发行以来,SQLAlchemy的"核心"已经内置了上述解决方案,可以在此处找到(下面的复制代码段):
1 2 3 4 5 6 7 8 9 10 11 12 | 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) |
根据phsource的回答,对于使用MySQL并完全覆盖同一键的数据而不执行
1 2 3 4 5 6 7 8 9 10 11 | 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 |
这取决于你。如果要替换,则在前缀中传递
1 2 3 4 5 6 7 8 9 10 11 | 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() |
此处的提交间隔可以更改为加速或减速
有一个更简单的解决方案:
1 2 3 4 5 6 7 8 9 10 | 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) |
我的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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() |
用法:
1 2 3 4 5 6 7 8 9 10 11 12 | myrepo.upsert([ { "field11":"value11", "field21":"value21", "field31":"value31", }, { "field12":"value12", "field22":"value22", "field32":"value32", }, ]) |
我只是使用普通的SQL作为:
1 2 | insert_stmt ="REPLACE INTO tablename (column1, column2) VALUES (:column_1_bind, :columnn_2_bind)" session.execute(insert_stmt, data) |
这些解决方案似乎都不是很优雅。蛮力方式是查询以查看该行是否存在。如果确实删除该行,然后插入,否则只需插入。显然涉及一些开销,但是它不依赖于修改原始sql,并且可以在非orm的东西上工作。