Filter SQLAlchemy query result object's one-to-many attribute

こ雲淡風輕ζ 提交于 2019-12-12 07:55:54

问题


Say I have a couple objects, having a one-to-many relationship, something like

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

Now, I want to query Parent objects, but have their children filtered by child_type, ie something like

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

This just returns the Parent with all the children, basically disregarding the filter. Is this result at all possible or do I have to also query Child?


回答1:


Indeed, your query adds a join and a filter, but returns only Parent instances. In fact, only those Parent instances which have at least one Child of type a.
When you then access .children on each of those parents, a new SQL statement will be issued and all children of that parent will be loaded. You can apply the filter again in memory, or create your own query and not rely on the relationship navigation (commented out) as below:

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

A way to do it in one query is shown below, but be careful as you are effectively telling sqlalchemy that you loaded all children for parents. You can use this approach for scenarios where you perform your query and then discard/recycle the session:

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



回答2:


You try to get two answers in one query. Either you can ask for all parents that have a type-a-child or you can ask for all type-a-children. In the first case, you have to filter the children again, if you want the corresponding children, in the second case you can simple get the corresponding parents. But which way is the correct one, depends on the further problem, you try to solve.



来源:https://stackoverflow.com/questions/23504889/filter-sqlalchemy-query-result-objects-one-to-many-attribute

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!