Domanda

Sto avviando una nuova applicazione e sto cercando di utilizzare un ORM, in particolare SQLAlchemy.

Supponi di avere una colonna "pippo" nel mio database e voglio aumentarla. In sqlite dritto, questo è facile:

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

Ho capito l'equivalente SQL Builder SQLAlchemy:

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)

Questo è leggermente più lento, ma non c'è molto in esso.

Ecco la mia ipotesi migliore per un approccio ORM di SQLAlchemy:

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

Fa la cosa giusta, ma ci vogliono poco meno di cinquanta volte finché gli altri due si avvicinano. Presumo che sia perché deve portare tutti i dati in memoria prima che possa funzionare con esso.

Esiste un modo per generare l'SQL efficiente utilizzando l'ORM di SQLAlchemy? O usando qualsiasi altro ORM Python? O dovrei semplicemente tornare a scrivere l'SQL a mano?

È stato utile?

Soluzione

L'ORM di SQLAlchemy è pensato per essere usato insieme al livello SQL, non per nasconderlo. Ma devi tenere a mente una o due cose quando usi l'ORM e il semplice SQL nella stessa transazione. Fondamentalmente, da un lato, le modifiche ai dati ORM colpiranno il database solo quando si cancellano le modifiche dalla sessione. Dall'altro lato, le istruzioni di manipolazione dei dati SQL non influiscono sugli oggetti presenti nella sessione.

Quindi se dici

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

farà quello che dice, andrà a prendere tutti gli oggetti dal database, modificherà tutti gli oggetti e poi quando è il momento di scaricare le modifiche al database, aggiorna le righe una alla volta.

Invece dovresti farlo:

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

Questo verrà eseguito come una query come ti aspetteresti, e poiché almeno la configurazione della sessione predefinita scade tutti i dati nella sessione al momento del commit non hai problemi di dati obsoleti.

Nella serie 0.5 quasi rilasciata è possibile utilizzare questo metodo anche per l'aggiornamento:

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

Ciò eseguirà sostanzialmente la stessa istruzione SQL del frammento precedente, ma selezionerà anche le righe modificate e farà scadere tutti i dati non aggiornati nella sessione. Se sai di non utilizzare alcun dato di sessione dopo l'aggiornamento, puoi anche aggiungere synchronize_session = False all'istruzione update e liberarti di quella selezione.

Altri suggerimenti

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

Prova questo =)

Esistono diversi modi per AGGIORNARE 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)

Ecco un esempio di come risolvere lo stesso problema senza dover mappare i campi 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()

Quindi, per aggiornare un'istanza Media, puoi fare qualcosa del genere:

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

Nonostante i test, proverei:

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

(IIRC, commit () funziona senza flush ()).

Ho scoperto che a volte fare una query di grandi dimensioni e poi iterare in Python può essere fino a 2 ordini di grandezza più veloce di molte query. Presumo che l'iterazione sull'oggetto query sia meno efficiente dell'iterazione su un elenco generato dal metodo all () dell'oggetto query.

[Nota il commento sotto - questo non ha accelerato affatto le cose].

Se è a causa del sovraccarico in termini di creazione di oggetti, probabilmente non può essere velocizzato affatto con SA.

Se è perché sta caricando oggetti correlati, allora potresti essere in grado di fare qualcosa con un caricamento lento. Ci sono molti oggetti creati a causa di riferimenti? (IE, ottenere un oggetto Azienda ottiene anche tutti gli oggetti Persone correlati).

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top