web-dev-qa-db-fra.com

Mise à jour efficace de la base de données à l'aide de SQLAlchemy ORM

Je démarre une nouvelle application et envisage d'utiliser un ORM, en particulier SQLAlchemy.

Supposons que j'ai une colonne 'foo' dans ma base de données et que je veuille l'incrémenter. En sqlite droite, c'est facile:

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

J'ai compris l'équivalent 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)

C'est un peu plus lent, mais il n'y en a pas beaucoup.

Voici ma meilleure estimation pour une approche 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()

Cela convient, mais cela prend un peu moins de cinquante fois plus de temps que les deux autres approches. Je suppose que c'est parce qu'il doit mettre toutes les données en mémoire avant de pouvoir fonctionner avec.

Existe-t-il un moyen de générer un SQL efficace en utilisant l'ORM de SQLAlchemy? Ou en utilisant un autre python ORM? Ou devrais-je simplement revenir à écrire le code SQL à la main?

99
John Fouhy

L'ORM de SQLAlchemy est destiné à être utilisé avec la couche SQL, pas à la masquer. Mais vous devez garder à l’esprit une ou deux choses lorsque vous utilisez ORM et le langage SQL simple dans la même transaction. Fondamentalement, d’un côté, les modifications de données ORM n’apparaîtront dans la base de données que si vous annulez les modifications de votre session. De l'autre côté, les instructions de manipulation de données SQL n'affectent pas les objets de votre session.

Donc si vous dites

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

il fera ce qu'il dit, va chercher tous les objets dans la base de données, modifiera tous les objets, puis, quand il sera temps de vider les modifications dans la base de données, mettra à jour les lignes une par une.

Au lieu de cela, vous devriez faire ceci:

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

Cela s'exécutera comme une requête, comme prévu, et comme au moins la configuration de session par défaut expire toutes les données de la session lors de la validation, vous n'avez aucun problème de données obsolète.

Dans la série 0.5 presque publiée, vous pouvez également utiliser cette méthode pour mettre à jour:

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

Cela exécutera fondamentalement la même instruction SQL que l'extrait précédent, mais sélectionnera également les lignes modifiées et expirera les données obsolètes de la session. Si vous savez que vous n'utilisez aucune donnée de session après la mise à jour, vous pouvez également ajouter synchronize_session=False à la déclaration de mise à jour et se débarrasser de cette sélection.

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

Essayez ceci =)

78
Vin

Il y a plusieurs façons de mettre à jour en utilisant 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)
19
Nima Soroush

Voici un exemple de résolution du même problème sans avoir à mapper les champs manuellement:

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):
  __table= '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()

Donc, pour mettre à jour une instance de média, vous pouvez faire quelque chose comme ceci:

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

Avec suffisamment de tests, je voudrais essayer:

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

(IIRC, commit () fonctionne sans flush ()).

J'ai constaté que, parfois, faire une requête volumineuse puis effectuer une itération dans python peut être jusqu'à 2 ordres de grandeur plus rapide que de nombreuses requêtes. Je suppose qu'itérer sur l'objet requête est moins efficace itération sur une liste générée par la méthode all () de l’objet requête.

[S'il vous plaît noter commentaire ci-dessous - cela n'a pas accéléré les choses du tout].

0

Si c'est à cause de la surcharge liée à la création d'objets, cela ne peut probablement pas être accéléré du tout avec SA.

Si c'est parce qu'il charge des objets liés, vous pourrez peut-être faire quelque chose avec un chargement paresseux. Y at-il beaucoup d'objets en cours de création en raison de références? (IE, obtenir un objet Company récupère également tous les objets People associés).

0