SQLAlchemy联表过滤后如何仅获取父对象下符合条件的关联子对象
方案1:单次查询临时过滤(无全局副作用,优先推荐)
该方案不会修改模型默认的关联规则,仅让本次查询返回的Parent实例的children属性,仅包含did_homework == True的子对象。
首先引入依赖:
from sqlalchemy.orm import contains_eager
查询写法:
parents = ( session.query(Parent) .join(Parent.children) # 关联Child表 .filter(Child.did_homework == True) # 筛选符合要求的Child .options(contains_eager(Parent.children)) # 告知ORM将本次查询关联的Child填充到Parent.children属性 .unique() # 去重,避免join导致的同一Parent重复返回 .all() )
此时执行good_children = parents[0].children,拿到的所有Child都会满足did_homework == True。
原查询不符合预期的核心逻辑:普通的
join + filter仅会筛选出“至少有一个完成作业的Child的Parent”,ORM加载Parent时,仍会按照relationship的默认配置,全量加载所有关联Child;contains_eager会直接复用本次查询中已经过滤后的Child结果填充到children属性中,替代默认的全量加载流程。
方案2:全局默认过滤(所有访问该关联的场景生效)
如果业务规则本身就要求Parent的children默认仅返回完成作业的Child,可以直接修改模型的relationship定义,增加固定关联条件:
from sqlalchemy import and_ class Parent(Base): __table__ = "parents" id = Column(Integer, primary_key=True) # 给relationship增加固定的过滤条件 children = relationship( "Child", back_populates="parent", primaryjoin=and_(Parent.id == Child.parent_id, Child.did_homework == True) )
修改后,所有场景下访问Parent.children都会自动返回完成作业的Child,无需额外编写查询逻辑。
注意:该方案存在全局副作用,如果后续需要获取Parent的全量Child列表,必须单独编写原生查询,无法直接通过
children属性获取。
备选:动态查询式关联
如果需要灵活给children追加不同的过滤条件,可以将relationship设置为动态查询模式:
class Parent(Base): __table__ = "parents" id = Column(Integer, primary_key=True) children = relationship("Child", back_populates="parent", lazy="dynamic")
此时访问Parent.children会返回查询器对象,可按需追加过滤逻辑:
# 获取完成作业的Child good_children = parents[0].children.filter(Child.did_homework == True).all() # 获取全量Child all_children = parents[0].children.all()
该方案灵活性较高,但不符合“直接调用children就能拿到过滤后列表”的需求,适合需要频繁调整关联过滤规则的场景。
内容的提问来源于stack exchange,提问作者Gudi

