我用Flask-SQLAlchemy定义了以下模型:
"""models.py"""
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
skill_candidate = db.Table(
'SkillCandidate',
db.Column('skill_id', db.String, db.ForeignKey('skill.id')),
db.Column('candidate_id', db.Integer, db.ForeignKey('candidate.id')))
class Candidate(db.Model):
id = db.Column(db.Integer, primary_key=True)
skills = db.relationship("Skill", secondary=skill_candidate)
class Skill(db.Model):
id = db.Column(db.String, primary_key=True)
name = db.Column(db.String, nullable=False, unique=True)
要达到的目标如下: 我想返回所有具有列表输入(甚至理想情况下,skill_id列表)中提供的技能的候选人。
我尝试了以下方法:
def get_skilled_candidates(skill_ids):
return Candidate.query.join(skill_candidate).\
filter(and_(*[skill_candidate.c.skill_id == skill_id for skill_id in skill_ids])).\
all()
目的是过滤所有技能的所有候选人,并用and_语句组成
如果我使用一个由1个项目组成的列表(返回具有该技能的所有候选者),则效果很好,但是如果我在输入列表中添加了更多技能(即使我在基础中有符合条件的候选者),也不能奏效< / p>
答案 0 :(得分:3)
您可以使用列表中的任何技能查询所有应聘者,然后使用列表理解来过滤结果。这可能不如@IljaEverilä所提到的关系划分方法那样有效,但是它无疑简化了查询方面。
skill_ids = ['id_1', 'id_2']
candidates = session.query(Candidate).\
filter(Candidate.skills.any(Skill.id.in_(skill_ids)).\
all()
candidates = [
c for c in candidates
if set(s.id for s in c.skills).issuperset(skill_ids)
]
答案 1 :(得分:3)
如评论中所述,您需要执行FORALL
操作(universal quantifier)或relational division。
FORALL x ( p(x) )
可以表示为
NOT ( EXISTS x ( NOT ( p(x) ) ) )
,如果您不了解FORALL
及其关系,则有点笨拙且难以推理。给定您的模型,它看起来像:
def get_skilled_candidates(skill_ids):
# Form a temporary derived table using unions
skills = db.union_all(*[
db.select([db.literal(sid).label('skill_id')])
for sid in skill_ids]).alias()
return Candidate.query.\
filter(
~db.exists().select_from(skills).where(
~db.exists().
where(db.and_(skill_candidate.c.skill_id == skills.c.skill_id,
skill_candidate.c.candidate_id == Candidate.id)).
correlate_except(skill_candidate))).\
all()
当然,还有其他表达相同查询的方法,例如:
def get_skilled_candidates(skill_ids):
return Candidate.query.\
join(skill_candidate).\
filter(skill_candidate.c.skill_id.in_(skill_ids)).\
group_by(Candidate.id).\
having(db.func.count(skill_candidate.c.skill_id.distinct()) ==
len(set(skill_ids))).\
all()
它实际上是通过计数检查所有技能ID是否匹配的。
如果使用Postgresql,您也可以这样做:
from sqlalchemy.dialects.postgresql import array_agg
def get_skilled_candidates(skill_ids):
# The double filtering may seem redundant, but the WHERE ... IN allows
# the query to use indexes, while the HAVING ... @> does the final filtering.
return Candidate.query.\
join(skill_candidate).\
filter(skill_candidate.c.skill_id.in_(skill_ids)).\
group_by(Candidate.id).\
having(array_agg(skill_candidate.c.skill_id).contains(skill_ids)).\
all()
这与其他答案中的部分Python解决方案等效。
此外,可以使用聚合EVERY
:
def get_skilled_candidates(skill_ids):
# Form a temporary derived table using unions
skills = db.union_all(*[
db.select([db.literal(sid).label('skill_id')])
for sid in skill_ids]).alias()
# Perform a CROSS JOIN between candidate and skills
return Candidate.query.\
join(skills, db.true()).\
group_by(Candidate.id).\
having(db.func.every(
db.exists().
where(db.and_(skill_candidate.c.skill_id == skills.c.skill_id,
skill_candidate.c.candidate_id == Candidate.id)).
correlate_except(skill_candidate))).\
all()