SQLAlchemy ORMを使用したデータベースの効率的な更新
-
06-07-2019 - |
質問
新しいアプリケーションを開始し、ORM、特にSQLAlchemyの使用を検討しています。
データベースに「foo」列があり、それをインクリメントしたいとします。ストレートsqliteでは、これは簡単です:
db = sqlite3.connect('mydata.sqlitedb')
cur = db.cursor()
cur.execute('update table stuff set foo = foo + 1')
SQLAlchemy SQLビルダーに相当するものを見つけました:
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)
これはわずかに遅くなりますが、それほど多くはありません。
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()
これは正しいことを行いますが、他の2つの方法が近づく限り、50倍もかかりません。これは、すべてのデータをメモリに取り込んでから操作する必要があるためだと思われます。
SQLAlchemyのORMを使用して効率的なSQLを生成する方法はありますか?または、他のPython ORMを使用していますか?または、手動でSQLの記述に戻る必要がありますか?
解決
SQLAlchemyのORMは、SQLレイヤーと一緒に使用するためのものであり、SQLレイヤーを非表示にするためのものではありません。ただし、同じトランザクションでORMとプレーンSQLを使用する場合は、1つまたは2つの点に留意する必要があります。基本的に、一方から、ORMデータの変更は、セッションから変更をフラッシュするときにのみデータベースにヒットします。一方、SQLデータ操作ステートメントは、セッション内のオブジェクトには影響しません。
だからあなたが言うなら
for c in session.query(Stuff).all():
c.foo = c.foo+1
session.commit()
指示どおりに実行し、データベースからすべてのオブジェクトをフェッチし、すべてのオブジェクトを変更してから、データベースへの変更をフラッシュするときに、行を1つずつ更新します。
代わりにこれを行う必要があります:
session.execute(update(stuff_table, values={stuff_table.c.foo: stuff_table.c.foo + 1}))
session.commit()
これは予想どおり1つのクエリとして実行され、少なくともデフォルトのセッション構成ではコミット時にセッション内のすべてのデータが期限切れになるため、古いデータの問題はありません。
ほとんどリリースされた0.5シリーズでは、このメソッドを使用して更新することもできます。
session.query(Stuff).update({Stuff.foo: Stuff.foo + 1})
session.commit()
基本的には前のスニペットと同じSQLステートメントを実行しますが、変更された行を選択し、セッション内の古いデータを失効させます。更新後にセッションデータを使用していないことがわかっている場合は、updateステートメントにsynchronize_session = Falseを追加して、その選択を削除することもできます。
他のヒント
session.query(Clients).filter(Clients.id == client_id_list).update({'status': status})
session.commit()
試してみてください=)
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)
フィールドを手動でマッピングすることなく同じ問題を解決する方法の例を次に示します。
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()
したがって、Mediaインスタンスを更新するには、次のようにします。
media = Media(id=123, title="Titular Line", slug="titular-line", type="movie")
media.update()
十分なテストを行ってみます:
for c in session.query(Stuff).all():
c.foo = c.foo+1
session.commit()
(IIRC、commit()はflush()なしで機能します。)
大規模なクエリを実行してからPythonで反復処理を行うと、多くのクエリよりも2桁高速になることがあります。クエリオブジェクトの反復は、クエリオブジェクトのall()メソッドによって生成されたリストの反復よりも効率が悪いと想定しています。
[以下のコメントに注意してください-これはまったくスピードアップしませんでした。]
オブジェクトの作成に関するオーバーヘッドが原因である場合、SAで速度を上げることはおそらくできないでしょう。
関連オブジェクトをロードしているためである場合、遅延ロードを使用して何かを実行できる場合があります。参照のために作成されているオブジェクトはたくさんありますか? (IE、Companyオブジェクトを取得すると、関連するPeopleオブジェクトもすべて取得されます)。