web-dev-qa-db-ja.com

重複キー更新時のSQLAlchemy

SQLAlchemyで_INSERT ... ON DUPLICATE KEY UPDATE_を行うエレガントな方法はありますか? inserter.insert().execute(list_of_dictionaries)に似た構文を持つ何かを意味しますか?

31
MrD

MySQLのON DUPLICATE KEY UPDATEポストバージョン1.2

この機能は現在、SQLAlchemy for MySQLにのみ組み込まれています。以下のsomada141の答えが最善の解決策です: https://stackoverflow.com/a/48373874/319066

SQLステートメントのON DUPLICATE KEY UPDATE

生成された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'文字列を含む挿入に自動的に変更することもできますが、ここでは怠惰であるため、ここでは行いません。

ORM内のON DUPLICATE KEY UPDATE機能

SQLAlchemyは、ORMレイヤーのON DUPLICATE KEY UPDATEまたはMERGEまたはその他の同様の機能へのインターフェイスを提供しません。それでも、問題のキーが主キーである場合にのみ機能を複製できる 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
39
phsource

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)
10
somada141

phsource's answer に基づいており、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
sheba

それはあなた次第です。置き換える場合は、接頭辞に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
Manoj Sahu

より簡単な解決策を得ました:

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
Frank He

私はプレーンSQLを次のように使用しました:

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