web-dev-qa-db-fra.com

SQLAlchemy ON DUPLICATE KEY UPDATE

Existe-t-il un moyen élégant de créer un INSERT ... ON DUPLICATE KEY UPDATE dans SQLAlchemy? Je veux dire quelque chose avec une syntaxe similaire à inserter.insert().execute(list_of_dictionaries)?

27
MrD

ON DUPLICATE KEY UPDATE après la version 1.2 pour MySQL

Cette fonctionnalité est maintenant intégrée à SQLAlchemy pour MySQL uniquement. La réponse ci-dessous de somada141 offre la meilleure solution: https://stackoverflow.com/a/48373874/319066

ON DUPLICATE KEY UPDATE dans l'instruction SQL

Si vous voulez que le SQL généré inclue réellement ON DUPLICATE KEY UPDATE, le moyen le plus simple consiste à utiliser un décorateur @compiles.

Le code (lié à un bon fil sur le sujet sur reddit ) pour un exemple peut être trouvé sur 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)

Mais notez que dans cette approche, vous devez créer manuellement append_string. Vous pourriez probablement changer la fonction append_string pour qu'elle change automatiquement la chaîne d'insertion en une insertion avec la chaîne 'ON DUPLICATE KEY UPDATE', mais je ne le ferai pas ici à cause de la paresse.

ON DUPLICATE KEY UPDATE fonctionnalité dans l'ORM

SQLAlchemy ne fournit pas d'interface avec ON DUPLICATE KEY UPDATE ou MERGE ni aucune autre fonctionnalité similaire dans sa couche ORM. Néanmoins, il possède la fonction session.merge() qui peut répliquer la fonctionnalité uniquement si la clé en question est une clé primaire.

session.merge(ModelObject) vérifie d'abord si une ligne avec la même valeur de clé primaire existe en envoyant une requête SELECT (ou en la recherchant localement). Si tel est le cas, il définit un indicateur quelque part indiquant que ModelObject est déjà dans la base de données et que SQLAlchemy doit utiliser une requête UPDATE. Notez que la fusion est un peu plus compliquée que cela, mais elle reproduit bien la fonctionnalité avec les clés primaires.

Mais que faire si vous voulez une fonctionnalité ON DUPLICATE KEY UPDATE avec une clé non primaire (par exemple, une autre clé unique)? Malheureusement, SQLAlchemy n’a pas une telle fonction. Au lieu de cela, vous devez créer quelque chose qui ressemble à la fonction get_or_create() de Django. Une autre réponse de StackOverflow le couvre , et je vais simplement coller une version modifiée et fonctionnelle ici pour plus de commodité.

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
37
phsource

Je devrais mentionner que depuis la version 1.2, le "noyau" de SQLAlchemy a une solution à ce qui précède, qui est intégrée et peut être vu sous ici (extrait copié ci-dessous):

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

Sur la base de (réponse de phsource }, et du cas d'utilisation spécifique de MySQL et du remplacement complet des données de la même clé sans effectuer une instruction DELETE, vous pouvez utiliser l'expression d'insertion décorée @compiles décorée suivante:

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

Cela dépend de vous. Si vous voulez remplacer, passez OR REPLACE dans les préfixes

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

Ici, l'intervalle de validation peut être modifié pour accélérer ou réduire

1
Manoj Sahu

Vous avez une solution plus simple:

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

Je viens d'utiliser plain SQL comme:

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