web-dev-qa-db-ja.com

sqlalchemy、IDのリストをオブジェクトのリストに変える

取得したいIDのシーケンスがあります。簡単です:

session.query(Record).filter(Record.id.in_(seq)).all()

それを行うためのより良い方法はありますか?

37
Cheery

あなたのコードはまったく問題ありません。

INORと結合されたX=Yの束のようなもので、現代のデータベースではかなり高速です。

ただし、IDのリストが長い場合は、IDのリストを返すサブクエリを渡すことで、クエリを少し効率的にすることができます。

22
Adam Dziendziel

そのままのコードは完全に問題ありません。ただし、誰かが私に、大きな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と、持っているレコードのローカル取得を行う関数を作成できます。これがそれです:

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
6
zzzeek

複合主キーを使用する場合は、次のようにTuple_を使用できます。

from sqlalchemy import Tuple_
session.query(Record).filter(Tuple_(Record.id1, Record.id2).in_(seq)).all()

これはSQLiteでは利用できないことに注意してください( doc を参照)。

4
G.J

もう1つの方法があります。問題のオブジェクトがすでにセッションにロードされていると予想するのが妥当な場合。同じトランザクションで以前にそれらにアクセスしたことがある場合は、代わりに次の操作を実行できます。

_map(session.query(Record).get, seq)
_

これらのオブジェクトがすでに存在する場合、これらのオブジェクトを取得するためのクエリがないため、これははるかに高速になります。一方、これらのオブジェクトの数が少ない場合notが読み込まれると、クエリが発生するため、処理速度が大幅に低下します。すべてのオブジェクトに対する単一のクエリではなく、欠落しているインスタンスごと。

これは、上記の手順に到達する前にjoinedload()クエリを実行しているときに役立つため、それらがすでにロードされていることを確認できます。一般に、デフォルトで質問のソリューションを使用する必要があり、同じオブジェクトを何度もクエリしていることがわかった場合にのみ、このソリューションを検討してください。

生成されるSQLを確認することをお勧めします。 str(query)を出力するだけで表示できます。

標準SQLでそれを行う理想的な方法を私は知りません。

1
iny