SQLAlchemy ON DUPLICATE KEY UPDATE

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

Ответ 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, и он работает на ненужных материалах.