SQLAlchemy中的contains_eager和limits

时间:2017-01-27 18:04:59

标签: python sqlalchemy

我有2个班级:

class A(Base):
    id = Column(Integer, primary_key=True)
    name = Column(String)
    children = relationship('B')
class B(Base):
    id = Column(Integer, primary_key=True)
    id_a = Column(Integer, ForeignKey('a.id'))
    name = Column(String)

现在我需要包含带有某个名称的B的所有对象A和一个对象将包含所有已过滤的B对象。

为了实现它,我构建了查询。

query = db.session.query(A).join(B).options(db.contains_eager(A.children)).filter(B.name=='SOME_TEXT')

现在我只需要50项查询,所以我这样做:

query.limit(50).all()

结果包含少于50,即使没有限制也超过50.我读了“渴望加载的禅”。但必须有一些技巧来实现它。我的一个想法是进行2次查询。一个使用内部连接来获取ID,然后在第一个查询中使用此ID。

但也许有更好的解决方法。

1 个答案:

答案 0 :(得分:2)

首先,退一步看看SQL。您当前的查询是

SELECT * FROM a JOIN b ON b.id_a = a.id WHERE b.name == '...' LIMIT 50;

请注意,此限制位于a JOIN b而非a,但如果您将限制设为a,则无法按b中的字段进行过滤。这个问题有两种解决方案。第一种是使用标量子查询来过滤b.name,如下所示:

SELECT * FROM a
WHERE EXISTS (SELECT 1 FROM b WHERE b.id_a = a.id AND b.name = '...')
LIMIT 50;

这可能效率低,具体取决于数据库后端。第二种解决方案是在加入后在a上进行DISTINCT,如下所示:

SELECT DISTINCT a.* FROM a JOIN b ON b.id_a = a.id
WHERE b.name == '...'
LIMIT 50;

请注意,在任何一种情况下,您都无法从b获取任何列。我们如何得到它们?再做一次加入!

SELECT * FROM (
    SELECT DISTINCT a.* FROM a JOIN b ON b.id_a = a.id
    WHERE b.name == '...'
    LIMIT 50;
) a JOIN b ON b.id_a = a.id
WHERE b.name == '...';

现在,在SQLAlchemy中编写所有这些:

subquery = (
    session.query(A)
           .join(B)
           .with_entities(A)  # only select A's columns
           .filter(B.name == '...')
           .distinct()
           .limit(50)
           .subquery()  # convert to subquery
)
aliased_A = aliased(A, subquery)
query = (
    session.query(aliased_A)
           .join(B)
           .options(contains_eager(aliased_A.children))
           .filter(B.name == "...")
)