2009-01-14 89 views
25

我有要检索的ID序列。这很简单:sqlalchemy,将ID列表转换为对象列表

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

有没有更好的方法来做到这一点?

+1

你不喜欢什么呢?它不起作用吗?它看起来应该。 – 2009-01-14 20:16:01

+0

它的工作原理,我只是想知道是否有更好的方法来做到这一点。 – Cheery 2009-01-14 20:27:52

回答

13

你的代码是绝对没问题的。

IN就像一堆X=Y加入OR,并且在当代数据库中速度相当快。

但是,如果您的ID列表很长,您可以通过传递一个返回ID列表的子查询来提高查询效率。

1

我建议看看它产生的SQL。你可以打印str(查询)来查看它。

我不知道使用标准SQL进行此操作的理想方式。

2

如果使用复合主键,你可以使用tuple_,如

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

请注意,这不是可在SQLite的(见doc)。

1

还有一种方法;如果期望所讨论的对象已经加载到会话中是合理的,您在同一交易之前访问了它们,你可以改为做:

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

在这些对象已经存在的情况下,这会快很多,因为不会有任何疑问,那些获取对象;另一方面,如果加载的对象中有不止一小部分是而不是,它会慢很多,因为它会导致每个缺少实例的查询,而不是所有对象的单个查询。

当您在执行joinedload()查询之前,在达到上述步骤之前,这可能很有用,因此您可以确定它们已被加载。通常,您应该在默认情况下在问题中使用解决方案,并且只有在看到您一遍又一遍地查询相同对象时才能探索此解决方案。

5

代码是完全正常的。然而,有人要求我在做大型IN的两种方法之间进行套期保值,而对个人ID使用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> 

这才是真正的世界中使用案件。

但是为了说明一些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