2011-07-07 16 views

Respuesta

33

ON DUPLICATE KEY UPDATE en la instrucción SQL

Si desea que el SQL generado para incluir realidad ON DUPLICATE KEY UPDATE, la forma más sencilla consiste en utilizar un decorador @compiles.

El código (vinculado desde un buen hilo sobre el tema on reddit) para un ejemplo se puede encontrar on 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) 

Pero tenga en cuenta que, en este enfoque, usted tiene que crear manualmente el append_string. Probablemente puedas cambiar la función append_string para que cambie automáticamente la cadena de inserción en una inserción con la secuencia 'ON DUPLICATE KEY UPDATE', pero no voy a hacer eso aquí debido a la pereza.

ON DUPLICATE KEY UPDATE funcionalidad dentro de la ORM

SQLAlchemy no proporciona una interfaz para ON DUPLICATE KEY UPDATE o MERGE o cualquier otra funcionalidad similar en su capa ORM. Sin embargo, tiene la función session.merge() que puede replicar la funcionalidad solo si la clave en cuestión es una clave principal.

session.merge(ModelObject) primero comprueba si existe una fila con el mismo valor de clave principal enviando una consulta SELECT (o buscándola localmente). Si lo hace, establece un indicador en algún lugar que indica que ModelObject ya está en la base de datos, y que SQLAlchemy debe usar una consulta UPDATE. Tenga en cuenta que fusionar es bastante más complicado que esto, pero replica bien la funcionalidad con claves primarias.

Pero, ¿qué ocurre si desea la funcionalidad ON DUPLICATE KEY UPDATE con una clave no primaria (por ejemplo, otra clave única)? Desafortunadamente, SQLAlchemy no tiene ninguna de esas funciones. En su lugar, debe crear algo que se asemeje al get_or_create() de Django. Another StackOverflow answer covers it, y solo pegaré una versión de trabajo modificada aquí para su comodidad.

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 
+1

Tenga en cuenta que la El código 'append_string' no es funcional en postgres (con su nueva función' ON CONFLICT [IGNORE | UPDATE] 'en 9.5, ya que ORM agrega automáticamente un' RETURNING {primary key} 'a las inserciones, y eso da como resultado SQL no válido. –

+0

¿Cómo puedo hacer que esto funcione con Postgres 9.5? – puredevotion

+0

¿Qué está haciendo la parte 'foo = foo' aquí y con qué reemplazaría' foo' en mi propia tabla? – nhinkle

1

Tienes una solución más 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) 
+7

Ten cuidado. 'REPLACE INTO' y' INSERT ... ON DUPLICATE KEY UPDATE' hacen cosas diferentes. –

+2

En particular, se elimina * * la fila, por lo que esta solución es por lo general terriblemente inútil en 'tablas InnoDB' (o cualquier otro motor transaccional), ya que se ahoga con la mayoría de cualquier' restricción externa KEY' – Naltharial

+0

Funciona bien con MySQL. Habiendo dicho eso, no tengo ninguna clave foránea en esa mesa. – algarecu

-1

Como ninguna de estas soluciones parecen aún elegante. Una forma de fuerza bruta es consultar para ver si existe la fila. Si elimina la fila y luego inserte de lo contrario simplemente inserte. Obviamente, se trata de una sobrecarga, pero no depende de modificar el sql en bruto y funciona en cosas no orm.

+1

Ya sabes, puedes ir al DBA-Hell por esto. – strangeqargo

0

acabo de utilizar SQL claro como:

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

Es depende de usted.Si desea reemplazar luego pasar OR REPLACE en prefijos

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

Aquí cometer intervalo se puede cambiar para acelerar o bajar la velocidad

1

Basado en phsource's answer, y para el caso de uso específico de usar MySQL y anulando por completo los datos para la misma clave sin realizar una declaración DELETE, se puede utilizar la siguiente expresión @compiles inserto decorado:

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 
0

debería mencionar que desde la liberación de la versión 1.2, la SQLAlchemy 'núcleo' tiene una solución a lo anterior con que se construye y se puede ver bajo here (fragmento copiado abajo):

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) 
Cuestiones relacionadas