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

10 投票
2 回答
9399 浏览
提问于 2025-04-18 05:36

假设我有几个对象,它们之间是一对多的关系,像这样:

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"))

现在,我想查询父对象,但希望它们的子对象根据子对象类型进行过滤,也就是说,像这样:

session.query(Parent).join(Parent.children).filter(Child.child_type == "a")

这样查询出来的结果是父对象和所有的子对象,基本上没有考虑到过滤条件。请问这样的结果有可能实现吗,还是说我也需要查询子对象?

2 个回答

1

你想在一个查询中得到两个答案。你可以问所有有类型A孩子的父母,或者你可以问所有类型A的孩子。在第一种情况下,如果你想得到对应的孩子,你还得再过滤一次孩子;而在第二种情况下,你可以直接得到对应的父母。不过,哪种方式更正确,取决于你想解决的具体问题。

9

确实,你的查询增加了一个连接和一个过滤条件,但只返回 Parent 实例。实际上,只有那些至少有一个类型为 aChildParent 实例。
当你访问每个父对象的 .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)

下面展示了一种在一个查询中完成的方式,但要小心,因为你实际上是在告诉 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)

撰写回答