update tutorial query example column python sqlalchemy upsert

python - tutorial - sqlalchemy selectable



¿Cómo hacer un upsert con SqlAlchemy? (3)

SQLAlchemy es compatible con ON CONFLICT ahora con dos métodos on_conflict_do_update() y on_conflict_do_nothing() :

Copia de la documentación:

from sqlalchemy.dialects.postgresql import insert stmt = insert(my_table).values(user_email=''[email protected]'', data=''inserted data'') stmt = stmt.on_conflict_do_update( index_elements=[my_table.c.user_email], index_where=my_table.c.user_email.like(''%@gmail.com''), set_=dict(data=stmt.excluded.data) ) conn.execute(stmt)

http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html?highlight=conflict#insert-on-conflict-upsert

Tengo un registro que quiero que exista en la base de datos si no está allí, y si ya está allí (la clave principal existe) quiero que los campos se actualicen al estado actual. Esto a menudo se llama un postre .

El siguiente fragmento de código incompleto demuestra lo que funcionará, pero parece excesivamente torpe (especialmente si había muchas más columnas). ¿Cuál es la mejor / mejor manera?

Base = declarative_base() class Template(Base): __tablename__ = ''templates'' id = Column(Integer, primary_key = True) name = Column(String(80), unique = True, index = True) template = Column(String(80), unique = True) description = Column(String(200)) def __init__(self, Name, Template, Desc): self.name = Name self.template = Template self.description = Desc def UpsertDefaultTemplate(): sess = Session() desired_default = Template("default", "AABBCC", "This is the default template") try: q = sess.query(Template).filter_by(name = desiredDefault.name) existing_default = q.one() except sqlalchemy.orm.exc.NoResultFound: #default does not exist yet, so add it... sess.add(desired_default) else: #default already exists. Make sure the values are what we want... assert isinstance(existing_default, Template) existing_default.name = desired_default.name existing_default.template = desired_default.template existing_default.description = desired_default.description sess.flush()

¿Hay una manera mejor o menos detallada de hacer esto? Algo como esto sería genial:

sess.upsert_this(desired_default, unique_key = "name")

aunque el unique_key unique_key es obviamente innecesario (el ORM debería ser capaz de resolverlo fácilmente) Lo agregué solo porque SQLAlchemy tiende a funcionar solo con la clave primaria. Por ejemplo: he estado viendo si Session.merge sería aplicable, pero esto solo funciona en la clave principal, que en este caso es una identificación autoincrementing que no es terriblemente útil para este propósito.

Un caso de uso de muestra para esto es simplemente cuando se inicia una aplicación de servidor que puede haber actualizado sus datos esperados por defecto. es decir: no hay problemas de concurrencia para este upsert.


SQLAlchemy tiene un comportamiento de "guardar o actualizar", que en versiones recientes se ha integrado en session.add , pero anteriormente era la llamada a session.saveorupdate . Esto no es un "postre" pero puede ser lo suficientemente bueno para sus necesidades.

Es bueno que pregunte por una clase con múltiples claves únicas; Creo que esta es precisamente la razón por la cual no existe una única forma correcta de hacerlo. La clave principal también es una clave única. Si no hubiera restricciones únicas, solo la clave principal, sería un problema bastante simple: si no existe nada con la ID dada, o si la ID es Ninguna, cree un nuevo registro; de lo contrario, actualice todos los demás campos en el registro existente con esa clave principal.

Sin embargo, cuando hay restricciones únicas adicionales, hay problemas lógicos con ese enfoque simple. Si desea "restablecer" un objeto, y la clave principal de su objeto coincide con un registro existente, pero otra columna única coincide con un registro diferente , entonces ¿qué hace? De forma similar, si la clave primaria no coincide con un registro existente, pero otra columna única coincide con un registro existente, ¿entonces qué? Puede haber una respuesta correcta para su situación particular, pero en general yo diría que no hay una sola respuesta correcta.

Esa sería la razón por la cual no hay una operación de "reposición" incorporada. La aplicación debe definir lo que esto significa en cada caso particular.


Utilizo un enfoque de "mirar antes de saltar":

# first get the object from the database if it exists # we''re guaranteed to only get one or zero results # because we''re filtering by primary key switch_command = session.query(Switch_Command)./ filter(Switch_Command.switch_id == switch.id)./ filter(Switch_Command.command_id == command.id).first() # If we didn''t get anything, make one if not switch_command: switch_command = Switch_Command(switch_id=switch.id, command_id=command.id) # update the stuff we care about switch_command.output = ''Hooray!'' switch_command.lastseen = datetime.datetime.utcnow() session.add(switch_command) # This will generate either an INSERT or UPDATE # depending on whether we have a new object or not session.commit()

La ventaja es que esto es db-neutral y creo que es claro de leer. La desventaja es que hay una condición de carrera potencial en un escenario como el siguiente:

  • consultamos el DB por un switch_command y no encontramos uno
  • creamos un switch_command
  • otro proceso o subproceso crea un switch_command con la misma clave principal que la nuestra
  • tratamos de comprometer nuestro switch_command