SQLAlchemy - 通过其childer查询父级,过滤连接以匹配列表中的每个子级

时间:2014-05-22 19:45:47

标签: python sqlalchemy

我正在尝试查询拥有所有请求技能的人员。不确定动态构建查询的最佳方法是什么。

ORM对象如下。人与技能之间的多对多,连接表不包括在这里:

Person
name=John, skills=[Skill(id=1)] # John knows Python
name=Eve, skills=[Skill(id=1), Skill(id=2)] # Eve knows Python and SQLAlchemy

Skill
id=1, name=Python
id=2, name=SQLAlchemy
id=3, name=Social skills

在搜索页面上,用户选择一项或多项技能,并根据此创建人员的搜索结果。让我们说用户想要找到一个知道Python和SQLAlchemy的人。应用程序从表单提交中获取两个ID的列表,[1,2]。我得到了以下有关工作的查询,找到具有匹配技能的人:

session.query(Person)./
join(Skill, Person.skills).filter(Skill.id.in_(list_of_skill_ids))

但是,使用in_子句会导致找到John人,因为他具有所需技能之一。但搜索结果应仅显示具有所有所需技能的人员。基本上我想要的是像in_(),但是用户而不是来匹配id列表。我可能应该用这个替换in_()部分,它不直接获取id列表:

filter(Person.id == 1, Person.id == 2)

但是,使用从表单收到的技能ID列表以编程方式构建查询的最简单方法是什么? id的数量可以是1..n。或者这是否是通过其子女查询父母的正确方法?

2 个答案:

答案 0 :(得分:5)

最直接的SA解决方案是为每项技能使用any

list_of_skill_ids = [1, 2]
qry = session.query(Person)
for skill_id in list_of_skill_ids:
    qry = qry.filter(Person.skills.any(Skill.id == skill_id))

print('\n'.join("{}".format(_) for _ in qry.all()))

这可能不是大数据集中性能最高的查询,但它非常干净。

另外,下面也应该有效。 (类似于RedBaron的答案,但将检查直接放在查询中):

qry = (session.query(Person)
        .join(Skill, Person.skills)
        .filter(Skill.id.in_(list_of_skill_ids))
        .group_by(Person)
        .having(len(list_of_skill_ids) <= func.count(Skill.id))
    )

print('\n'.join("{}".format(_) for _ in qry.all()))

答案 1 :(得分:1)

一种方法可以是找到满足每个技能组的人员列表,然后获取所有列表的交集。但在这种情况下的缺点是,如果你必须搜索10个技能组,你需要发出10个查询。

更好的方法是按人员ID对原始查询进行分组,然后计算每个人的技能数量。只保留技能数量与您的技能组合相匹配的人员。也许是这样的?

skill_count = session.query(Person.id.label('id'),func.count(Skill.id.distinct()).label('scount')).\
              select_from(Person).\
              join(Person.skills).\
              filter(Skill.id.in_(list_of_skill_ids)).\
              group_by(Person.id).all()
matching_pid_list = [x.id for x in skill_count if x.scount == len(list_of_skill_ids)]