过滤SQLAlchemy查询结果对象的一对多属性

bea*_*ans 8 python orm filtering sqlalchemy one-to-many

假设我有几个对象,有一对多的关系,就像

class Parent():
    //id, other cols, etc
    children = relationship("Child", backref="parent")

class Child():
    parent_id = Column(Integer, ForeignKey("parent.id")
    child_type = Column(Enum("a","b"))
Run Code Online (Sandbox Code Playgroud)

现在,我想查询Parent对象,但让他们的子进程按child_type进行过滤,即类似

session.query(Parent).join(Parent.children).filter(Child.child_type == "a")
Run Code Online (Sandbox Code Playgroud)

这只会返回所有子节点的Parent,基本上忽略了过滤器.这个结果是否可能,或者我是否还要查询Child?

van*_*van 8

实际上,您的查询会添加连接和过滤器,但只返回Parent实例.实际上,只有那些Parent至少有一种Child类型的实例a.
然后,当您访问.children每个父项时,将发出一个新的SQL语句,并且将加载该父项的所有子项.您可以在内存中再次应用过滤器,或创建自己的查询,而不是依赖关系导航(注释掉),如下所示:

# select *only* those parents who have at least one child of type "a"
parents = session.query(Parent).join(Parent.children).filter(Child.child_type == "a")
for p in parents:
    # 1. in-memory filter: now select only type "a" children for each parent
    children_a = [c for c in p.children if c.child_type == 'a']
    # 2. custom query: now select only type "a" children for each parent
    # children_a = session.query(Child).with_parent(p).filter(Child.child_type == "a")

    print("AAA", p)
    for c in children_a:
        print("AAA ..", c)
Run Code Online (Sandbox Code Playgroud)

在一个查询中执行此操作的方法如下所示,但要小心,因为您有效地告诉sqlalchemy您为父母加载了所有子项.您可以将此方法用于执行查询然后丢弃/回收会话的方案:

# select all parents, and eager-load children of type "a"
parents = (session.query(Parent)
        .join(Parent.children).filter(Child.child_type == "a")
        # make SA think we loaded all *parent.children* collection
        .options(contains_eager('children'))
        )

for p in parents:
    children_a = p.children # now *children* are *incorrectly* filtered
    print("BBB", p)
    for c in children_a:
        print("BBB ..", c)
Run Code Online (Sandbox Code Playgroud)


Dan*_*iel 1

您尝试在一个查询中获得两个答案。您可以询问所有拥有 A 型孩子的父母,也可以询问所有 A 型孩子。在第一种情况下,你必须再次过滤孩子,如果你想要相应的孩子,在第二种情况下你可以简单地获取相应的父母。但哪种方法是正确的,取决于您尝试解决的进一步问题。