SQLAlchemy ON DUPLICATE KEY UPDATE - PullRequest
       19

SQLAlchemy ON DUPLICATE KEY UPDATE

27 голосов
/ 07 июля 2011

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

Ответы [ 7 ]

39 голосов
/ 12 мая 2012

ON DUPLICATE KEY UPDATE post-1.2 для MySQL

Эта функциональность теперь встроена только в SQLAlchemy для MySQL.Ответ somada141 ниже имеет лучшее решение: https://stackoverflow.com/a/48373874/319066

ON DUPLICATE KEY UPDATE в операторе SQL

Если вы хотите, чтобы сгенерированный SQL действительно включал ON DUPLICATE KEY UPDATE, самый простой способ заключается в использовании @compiles decorator.

Код (связанный с хорошей веткой по теме на 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(). Другой ответ 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
8 голосов
/ 22 января 2018

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

На основании ответа 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 голос
/ 04 февраля 2016

Это зависит от вас. Если вы хотите заменить, тогда передайте 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 голос
/ 01 августа 2012

Получил более простое решение:

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)
0 голосов
/ 31 июля 2015

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

insert_stmt = "REPLACE INTO tablename (column1, column2) VALUES (:column_1_bind, :columnn_2_bind) "
session.execute(insert_stmt, data)
0 голосов
/ 29 июня 2013

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...