sqlalchemy, превращая список идентификаторов в список объектов - PullRequest
33 голосов
/ 14 января 2009

У меня есть последовательность идентификаторов, которые я хочу получить. Все просто:

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

Есть ли лучший способ сделать это?

Ответы [ 5 ]

17 голосов
/ 19 января 2009

Твой код в порядке.

IN подобен группе X=Y, объединенной с OR, и довольно быстро работает в современных базах данных.

Однако, если ваш список идентификаторов длинный, вы можете сделать запрос немного более эффективным, передав подзапрос, возвращающий список идентификаторов.

6 голосов
/ 06 февраля 2015

Код как есть, полностью в порядке. Тем не менее, кто-то спрашивает меня о некоторой системе хеджирования между двумя подходами - делать большие IN и использовать get () для отдельных идентификаторов.

Если кто-то действительно пытается избежать 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>

Это реальный пример использования.

Но чтобы проиллюстрировать некоторый API-интерфейс SQLAlchemy, мы можем создать функцию, которая выполняет 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
4 голосов
/ 21 января 2014

Если вы используете составные первичные ключи, вы можете использовать tuple_, как в

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

Обратите внимание, что это недоступно в SQLite (см. doc ).

1 голос
/ 21 января 2014

Есть еще один способ; Если разумно ожидать, что рассматриваемые объекты уже загружены в сеанс; вы обращались к ним ранее в той же транзакции, вместо этого вы можете сделать:

map(session.query(Record).get, seq)

В случае, если эти объекты уже присутствуют, это будет намного быстрее, так как не будет никаких запросов для извлечения этих объектов; С другой стороны, если не загружено более, чем крошечное число этих объектов, это будет намного, намного медленнее, поскольку вызовет запрос для каждого пропущенного экземпляра, а не один запрос для всех объектов. .

Это может быть полезно, когда вы выполняете joinedload() запросов до достижения вышеуказанного шага, поэтому вы можете быть уверены, что они уже были загружены. В общем, вы должны использовать решение в вопросе по умолчанию и исследовать это решение только тогда, когда вы видите, что запрашиваете одни и те же объекты снова и снова.

1 голос
/ 14 января 2009

Я бы рекомендовал взглянуть на SQL, который он производит. Вы можете просто напечатать str (запрос), чтобы увидеть его.

Я не знаю идеального способа сделать это с помощью стандартного SQL.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...