Есть ли элегантный способ сделать INSERT .. . ON DUPLICATE KEY UPDATE
в SQLAlchemy? Я имею в виду что-то с синтаксисом, похожим на insertter.insert (). Execute (list_of_ dictionaries)
?
ON DUPLICATE KEY UPDATE
после версии 1.2 для MySQL Эта функциональность теперь встроена только в SQLAlchemy для MySQL. Ответ somada141 ниже имеет лучшее решение: https://stackoverflow.com/a/48373874/319066
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 такой функции нет. Вместо этого вы должны создать что-то похожее на Джанго get_or_create()
. Другой ответ StackOverflow покрывает его , и я просто вставлю здесь модифицированную рабочую версию для удобства.
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
На основе ответа источника phs и для конкретного случая использования 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
Получил более простое решение:
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)
Поскольку ни одно из этих решений не кажется элегантным. Метод грубой силы - запросить, существует ли строка. Если это действительно удалить строку, а затем вставить в противном случае просто вставьте. Очевидно, что это связано с некоторыми накладными расходами, но оно не зависит от модификации исходного sql и работает с не-нормальными вещами.