我有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。
但也许有更好的解决方法。
答案 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 == "...")
)