Question

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

Dites que j'ai une colonne 'foo' dans ma base de données et que je veux 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 découvert 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 a pas grand chose dedans.

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 ORM python? Ou devrais-je simplement revenir à écrire le code SQL à la main?

Était-ce utile?

La solution

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 l'ORM et le SQL pur 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 va faire ce qu'il dit, aller chercher tous les objets dans la base de données, modifier tous les objets, puis quand il est temps de vider les modifications dans la base de données, mettre à 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()

Ceci 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 la mise à 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 plus de données de session après la mise à jour, vous pouvez également ajouter synchronize_session = False à l'instruction update et vous en débarrasser.

Autres conseils

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

Essayez ceci =)

Il existe plusieurs façons de mettre à jour à l'aide de 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)

Voici un exemple de solution au 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):
  __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()

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

Avec suffisamment de tests, j'essaierais:

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

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

J'ai constaté que, par moments, une requête volumineuse puis une itération en python pouvaient être jusqu'à 2 ordres de grandeur plus rapides que beaucoup de requêtes. Je suppose que itérer sur l'objet de requête est moins efficace que sur une liste générée par la méthode all () de l'objet de requête.

[Veuillez noter le commentaire ci-dessous - cela n'a pas accéléré les choses du tout].

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? (Par exemple, obtenir un objet Société obtient également tous les objets Personnes associés.)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top