it-swarm.com.de

Effizientes Aktualisieren der Datenbank mit SQLAlchemy ORM

Ich starte eine neue Anwendung und möchte ein ORM verwenden, insbesondere SQLAlchemy.

Angenommen, ich habe eine Spalte 'foo' in meiner Datenbank und möchte diese erhöhen. In Straight SQLite ist dies einfach:

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

Ich habe das SQLAlchemy SQL-Builder-Äquivalent herausgefunden:

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)

Das ist etwas langsamer, aber es ist nicht viel drin.

Hier ist meine beste Vermutung für einen SQLAlchemy ORM-Ansatz:

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

Dies macht das Richtige, aber es dauert knapp fünfzig Mal so lange wie die beiden anderen Ansätze. Ich nehme an, das liegt daran, dass es alle Daten in den Speicher bringen muss, bevor es damit arbeiten kann.

Gibt es eine Möglichkeit, mit SQLAlchemys ORM effizientes SQL zu generieren? Oder mit einem anderen python ORM? Oder soll ich einfach wieder von Hand SQL schreiben?

99
John Fouhy

SQLAlchemys ORM soll zusammen mit der SQL-Ebene verwendet werden, nicht verbergen. Sie müssen jedoch ein oder zwei Dinge beachten, wenn Sie ORM und einfaches SQL in derselben Transaktion verwenden. Grundsätzlich werden ORM-Datenänderungen von einer Seite nur dann in die Datenbank übernommen, wenn Sie die Änderungen aus Ihrer Sitzung entfernen. Andererseits wirken sich SQL-Datenmanipulationsanweisungen nicht auf die Objekte in Ihrer Sitzung aus.

Also wenn du sagst

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

es wird tun, was es sagt, alle Objekte aus der Datenbank abrufen, alle Objekte ändern und dann, wenn es Zeit ist, die Änderungen in die Datenbank zu schreiben, die Zeilen nacheinander aktualisieren.

Stattdessen sollten Sie dies tun:

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

Dies wird erwartungsgemäß als eine Abfrage ausgeführt, und da mindestens bei der Standardsitzungskonfiguration alle Daten in der Sitzung beim Festschreiben ablaufen, treten keine veralteten Datenprobleme auf.

In der fast veröffentlichten 0.5-Serie können Sie diese Methode auch zum Aktualisieren verwenden:

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

Das wird im Grunde die gleiche SQL-Anweisung wie das vorherige Snippet ausführen, aber auch die geänderten Zeilen auswählen und alle veralteten Daten in der Sitzung verfallen lassen. Wenn Sie wissen, dass Sie nach dem Update keine Sitzungsdaten verwenden, können Sie der Update-Anweisung auch synchronize_session=False Hinzufügen und diese Auswahl entfernen.

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

Versuchen Sie dies =)

78
Vin

Es gibt verschiedene Möglichkeiten, ein UPDATE mit sqlalchemy durchzuführen

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

Hier ist ein Beispiel, wie Sie dasselbe Problem lösen können, ohne die Felder manuell zuordnen zu müssen:

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://[email protected]: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()

So können Sie eine Medieninstanz aktualisieren:

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

Ohne zu testen, würde ich versuchen:

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

(IIRC, commit () funktioniert ohne flush ()).

Ich habe festgestellt, dass das Ausführen einer großen Abfrage und anschließendes Iterieren in python bis zu 2 Größenordnungen schneller sein kann als viele Abfragen. Ich gehe davon aus, dass das Iterieren über das Abfrageobjekt weniger effizient ist als über eine Liste iterieren, die von der all () -Methode des Abfrageobjekts generiert wurde.

[Bitte beachten Sie den Kommentar unten - dies hat die Dinge überhaupt nicht beschleunigt].

0

Wenn es an dem Mehraufwand beim Erstellen von Objekten liegt, kann es mit SA wahrscheinlich überhaupt nicht beschleunigt werden.

Wenn dies daran liegt, dass verwandte Objekte geladen werden, können Sie möglicherweise etwas mit verzögertem Laden anfangen. Werden aufgrund von Referenzen viele Objekte erstellt? (IE, beim Abrufen eines Company-Objekts werden auch alle zugehörigen People-Objekte abgerufen).

0