sqlalchemy、IDのリストをオブジェクトのリストに変換
-
22-07-2019 - |
質問
取得したいIDのシーケンスがあります。簡単です:
session.query(Record).filter(Record.id.in_(seq)).all()
より良い方法はありますか?
解決
あなたのコードはまったく問題ありません。
IN
は、 OR =で結合された
X = Y
の束のようなもので、現代のデータベースではかなり高速です。
ただし、IDのリストが長い場合は、IDのリストを返すサブクエリを渡すことで、クエリをもう少し効率的にすることができます。
他のヒント
コードはそのままで問題ありません。ただし、大きなINを実行する方法と個々のIDにget()を使用する方法の2つのアプローチの間のヘッジシステムについて、誰かが私に尋ねています。
誰かが本当にSELECTを回避しようとしている場合、それを行うための最善の方法は、必要なオブジェクトを事前にメモリにセットアップすることです。たとえば、要素の大きなテーブルで作業しています。作業をチャンクに分割します。たとえば、すべての作業を主キーまたは日付範囲などで並べ替え、そのチャンクのすべてをキャッシュにローカルにロードします。
all_ids = [<huge list of ids>]
all_ids.sort()
while all_ids:
chunk = all_ids[0:1000]
# bonus exercise! Throw each chunk into a multiprocessing.pool()!
all_ids = all_ids[1000:]
my_cache = dict(
Session.query(Record.id, Record).filter(
Record.id.between(chunk[0], chunk[-1]))
)
for id_ in chunk:
my_obj = my_cache[id_]
<work on my_obj>
これが実際の使用例です。
しかし、いくつかのSQLAlchemy APIを説明するために、所有していないレコードに対してINを実行する関数を作成し、実行しているレコードに対してローカルgetを実行できます。これは次のとおりです。
from sqlalchemy import inspect
def get_all(session, cls, seq):
mapper = inspect(cls)
lookup = set()
for ident in seq:
key = mapper.identity_key_from_primary_key((ident, ))
if key in session.identity_map:
yield session.identity_map[key]
else:
lookup.add(ident)
if lookup:
for obj in session.query(cls).filter(cls.id.in_(lookup)):
yield obj
デモは次のとおりです。
from sqlalchemy import Column, Integer, create_engine, String
from sqlalchemy.orm import Session
from sqlalchemy.ext.declarative import declarative_base
import random
Base = declarative_base()
class A(Base):
__tablename__ = 'a'
id = Column(Integer, primary_key=True)
data = Column(String)
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
ids = range(1, 50)
s = Session(e)
s.add_all([A(id=i, data='a%d' % i) for i in ids])
s.commit()
s.close()
already_loaded = s.query(A).filter(A.id.in_(random.sample(ids, 10))).all()
assert len(s.identity_map) == 10
to_load = set(random.sample(ids, 25))
all_ = list(get_all(s, A, to_load))
assert set(x.id for x in all_) == to_load
複合主キーを使用する場合、のように tuple _
を使用できます
from sqlalchemy import tuple_
session.query(Record).filter(tuple_(Record.id1, Record.id2).in_(seq)).all()
これはSQLiteでは使用できないことに注意してください( doc )。
生成されるSQLを確認することをお勧めします。表示するにはstr(query)を印刷するだけです。
標準SQLでそれを行う理想的な方法を知りません。
もう1つの方法があります。問題のオブジェクトがすでにセッションにロードされていると予想するのが妥当な場合。同じトランザクションで以前にアクセスしたことがある場合は、代わりに次の操作を実行できます。
map(session.query(Record).get, seq)
これらのオブジェクトが既に存在する場合、これらのオブジェクトを取得するためのクエリがないため、これははるかに高速になります。一方、これらのオブジェクトが少数しかロードされていない場合は、すべての単一のクエリではなく、インスタンスの欠落ごとにクエリが発生するため、はるかに遅くなりますオブジェクト。
これは、上記のステップに到達する前に joinedload()
クエリを実行している場合に役立ちます。したがって、すでにロードされていることを確認できます。一般に、デフォルトでは質問のソリューションを使用する必要があり、同じオブジェクトを何度も照会していることがわかった場合にのみこのソリューションを検討してください。