Вопрос: SQLAlchemy ON DUPLICATE KEY UPDATE


Есть ли элегантный способ сделать INSERT ... ON DUPLICATE KEY UPDATE в SQLAlchemy? Я имею в виду что-то с синтаксисом, подобным inserter.insert().execute(list_of_dictionaries) ?


25


источник


Ответы:


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's 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

34



Я должен упомянуть, что с момента выпуска версии 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)

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()

Здесь можно изменить интервал фиксации для ускорения или ускорения


1



Основано на Ответ phsource , а для конкретного случая использования 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

1



Я просто использовал простой sql как:

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

0



Поскольку ни одно из этих решений не выглядит элегантным. Метод грубой силы - запрашивать, существует ли строка. Если он удалит строку, а затем вставить иначе, просто вставьте. Очевидно, что некоторые накладные расходы связаны, но он не полагается на модификацию raw sql, и он работает на non orm.


-1