Есть ли элегантный способ сделать INSERT ... ON DUPLICATE KEY UPDATE
в SQLAlchemy? Я имею в виду что-то с синтаксисом, похожим на inserter.insert().execute(list_of_dictionaries)
?
SQLAlchemy ON DUPLICATE KEY UPDATE
Ответ 1
ON DUPLICATE KEY UPDATE
в операторе SQL
Если вы хотите, чтобы сгенерированный SQL действительно включал ON DUPLICATE KEY UPDATE
, самый простой способ заключается в использовании декоратора @compiles
.
Код (связанный с хорошей нитью по теме в reddit) для примера можно найти в 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", но я не собираюсь делать это из-за лени.
ON DUPLICATE KEY UPDATE
в ORM
SQLAlchemy не предоставляет интерфейс для ON DUPLICATE KEY UPDATE
или MERGE
или любых других подобных функций на своем уровне ORM. Тем не менее, у него есть функция session.merge()
, которая может реплицировать функциональность только в том случае, если данный ключ является первичным ключом.
session.merge(ModelObject)
сначала проверяет, существует ли строка с тем же значением первичного ключа, отправив запрос SELECT
(или просмотрев его локально). Если это так, он устанавливает флаг где-то, указывающий, что ModelObject уже находится в базе данных, и что SQLAlchemy должен использовать запрос UPDATE
. Обратите внимание, что слияние довольно немного сложнее, чем это, но оно хорошо реплицирует функциональность с помощью первичных ключей.
Но что, если вы хотите ON DUPLICATE KEY UPDATE
функциональность с непервичным ключом (например, еще один уникальный ключ)? К сожалению, SQLAlchemy не имеет такой функции. Вместо этого вам нужно создать что-то похожее на Django get_or_create()
. fooobar.com/questions/15346/..., и я просто добавлю измененную рабочую версию этого здесь для удобства.
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
Я должен упомянуть, что с момента выпуска версии v1.2 ядро SQLAlchemy имеет решение выше, с встроенным и можно увидеть здесь (скопированный фрагмент ниже):
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)
Ответ 3
Получено более простое решение:
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)
Ответ 4
Это зависит от вас. Если вы хотите заменить, то передайте 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()
Здесь можно изменить интервал фиксации для ускорения или ускорения
Ответ 5
На основе ответа phsource, а также для конкретного варианта использования MySQL и полного переопределения данных для одного и того же ключа без выполнения a 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
Ответ 6
Я просто использовал простой sql как:
insert_stmt = "REPLACE INTO tablename (column1, column2) VALUES (:column_1_bind, :columnn_2_bind) "
session.execute(insert_stmt, data)
Ответ 7
Поскольку ни одно из этих решений не выглядит элегантным. Метод грубой силы - запрашивать, существует ли строка. Если он удалит строку, а затем вставить иначе, просто вставьте. Очевидно, что некоторые накладные расходы связаны, но он не полагается на модификацию сырого sql, и он работает на ненужных материалах.