Pregunta

Estoy iniciando una nueva aplicación y estoy buscando usar un ORM, en particular, SQLAlchemy.

Digamos que tengo una columna 'foo' en mi base de datos y quiero incrementarla. En sqlite directo, esto es fácil:

db = sqlite3.connect('mydata.sqlitedb')
cur = db.cursor()
cur.execute('update table stuff set foo = foo + 1')

Descubrí el equivalente de SQLAlchemy SQL-builder:

engine = sqlalchemy.create_engine('sqlite:///mydata.sqlitedb')
md = sqlalchemy.MetaData(engine)
table = sqlalchemy.Table('stuff', md, autoload=True)
upd = table.update(values={table.c.foo:table.c.foo+1})
engine.execute(upd)

Esto es un poco más lento, pero no contiene mucho.

Aquí está mi mejor conjetura para un enfoque SQLAlchemy ORM:

# snip definition of Stuff class made using declarative_base
# snip creation of session object
for c in session.query(Stuff):
    c.foo = c.foo + 1
session.flush()
session.commit()

Esto hace lo correcto, pero lleva un poco menos de cincuenta veces más que los otros dos enfoques. Supongo que es porque tiene que traer todos los datos a la memoria antes de que pueda trabajar con ellos.

¿Hay alguna forma de generar el SQL eficiente usando el ORM de SQLAlchemy? ¿O utilizando cualquier otro python ORM? ¿O debería volver a escribir el SQL a mano?

¿Fue útil?

Solución

El ORM de SQLAlchemy está diseñado para usarse junto con la capa SQL, no para ocultarlo. Pero debe tener en cuenta una o dos cosas al usar el ORM y el SQL simple en la misma transacción. Básicamente, por un lado, las modificaciones de datos ORM solo afectarán a la base de datos cuando elimine los cambios de su sesión. Por otro lado, las declaraciones de manipulación de datos SQL no afectan los objetos que están en su sesión.

Entonces, si dices

for c in session.query(Stuff).all():
    c.foo = c.foo+1
session.commit()

hará lo que dice, vaya a buscar todos los objetos de la base de datos, modifique todos los objetos y luego, cuando sea el momento de eliminar los cambios en la base de datos, actualice las filas una por una.

En su lugar, debe hacer esto:

session.execute(update(stuff_table, values={stuff_table.c.foo: stuff_table.c.foo + 1}))
session.commit()

Esto se ejecutará como una consulta como cabría esperar, y debido a que al menos la configuración de sesión predeterminada caduca todos los datos en la sesión al confirmar, no tiene ningún problema de datos obsoletos.

En la serie 0.5 casi lanzada, también podría usar este método para actualizar:

session.query(Stuff).update({Stuff.foo: Stuff.foo + 1})
session.commit()

Eso básicamente ejecutará la misma instrucción SQL que el fragmento anterior, pero también seleccionará las filas modificadas y caducará cualquier dato obsoleto en la sesión. Si sabe que no está utilizando ningún dato de sesión después de la actualización, también puede agregar synchronize_session = False a la declaración de actualización y deshacerse de esa selección.

Otros consejos

session.query(Clients).filter(Clients.id == client_id_list).update({'status': status})
session.commit()

Prueba esto =)

Hay varias formas de ACTUALIZAR usando sqlalchemy

1) for c in session.query(Stuff).all():
       c.foo += 1
   session.commit()

2) session.query().\
       update({"foo": (Stuff.foo + 1)})
   session.commit()

3) conn = engine.connect()
   stmt = Stuff.update().\
       values(Stuff.foo = (Stuff.foo + 1))
   conn.execute(stmt)

Aquí hay un ejemplo de cómo resolver el mismo problema sin tener que asignar los campos manualmente:

from sqlalchemy import Column, ForeignKey, Integer, String, Date, DateTime, text, create_engine
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm.attributes import InstrumentedAttribute

engine = create_engine('postgres://postgres@localhost:5432/database')
session = sessionmaker()
session.configure(bind=engine)

Base = declarative_base()


class Media(Base):
  __tablename__ = 'media'
  id = Column(Integer, primary_key=True)
  title = Column(String, nullable=False)
  slug = Column(String, nullable=False)
  type = Column(String, nullable=False)

  def update(self):
    s = session()
    mapped_values = {}
    for item in Media.__dict__.iteritems():
      field_name = item[0]
      field_type = item[1]
      is_column = isinstance(field_type, InstrumentedAttribute)
      if is_column:
        mapped_values[field_name] = getattr(self, field_name)

    s.query(Media).filter(Media.id == self.id).update(mapped_values)
    s.commit()

Entonces, para actualizar una instancia de medios, puede hacer algo como esto:

media = Media(id=123, title="Titular Line", slug="titular-line", type="movie")
media.update()

Sin pruebas, probaría:

for c in session.query(Stuff).all():
     c.foo = c.foo+1
session.commit()

(IIRC, commit () funciona sin flush ()).

He descubierto que, a veces, hacer una consulta grande y luego iterar en Python puede ser hasta 2 órdenes de magnitud más rápido que muchas consultas. Supongo que iterar sobre el objeto de consulta es menos eficiente que iterar sobre una lista generada por el método all () del objeto de consulta.

[Tenga en cuenta el comentario a continuación, esto no aceleró las cosas en absoluto].

Si se debe a la sobrecarga en términos de creación de objetos, entonces probablemente no se pueda acelerar con SA.

Si es porque está cargando objetos relacionados, es posible que pueda hacer algo con la carga diferida. ¿Se están creando muchos objetos debido a referencias? (Es decir, obtener un objeto de empresa también obtiene todos los objetos de personas relacionados).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top