我们说我有一个模型工具箱,与各种工具相关的外键:
class Toolbox(db.Model):
id = db.Column(db.Integer, primary_key=True)
tools = db.relationship('Tools')
class Tool(db.Model):
id = db.Column(db.Integer)
name = db.Column(db.String(64), index=True)
quantity = db.Column(db.Integer)
toolbox_id = db.Column(db.Integer, db.ForeignKey('toolbox.id'))
toolbox = db.relationship(Toolbox)
以及工具及其数量的字典:
tool_dict = {'screwdriver' : 3, 'wrench' : 1 }
如何在SQLAlchemy中进行查询,返回包含至少3个螺丝刀和1个扳手的工具箱列表?
我到目前为止:
t = Toolbox.query.join(Tool).filter(Tool.name.in_(tool_dict.keys()))
然而,无论数量多少,都会返回包含螺丝刀和扳手的所有工具箱。
答案 0 :(得分:0)
解决此问题的一种方法是首先从tool
关系中选择一组(toolbox_id, name)
元组的并集,其中quantity
被满足。然后找到divide设置了所需工具的名称,并留下您想要的toolbox_id
。
另一方面,在这种情况下,更简单的方法是从满足数量的工具中选择intersection工具箱ID,然后选择与ID匹配的工具箱。
In [51]: db.session.add(Toolbox(tools=[
...: Tool(name='hammer', quantity=1),
...: Tool(name='screwdriver', quantity=3),
...: Tool(name='wrench', quantity=1)]))
In [52]: db.session.add(Toolbox(tools=[
...: Tool(name='hammer', quantity=1),
...: Tool(name='screwdriver', quantity=2),
...: Tool(name='wrench', quantity=1)]))
In [53]: db.session.add(Toolbox(tools=[
...: Tool(name='hammer', quantity=1),
...: Tool(name='screwdriver', quantity=3)]))
In [54]: db.session.add(Toolbox(tools=[
...: Tool(name='hammer', quantity=1),
...: Tool(name='wrench', quantity=1)]))
In [55]: db.session.add(Toolbox(tools=[
...: Tool(name='hammer', quantity=1)]))
In [56]: db.session.commit()
形成十字路口:
In [9]: tools = db.intersect(*(db.session.query(Tool.toolbox_id).
...: filter(Tool.name == name,
...: Tool.quantity >= quantity)
...: for name, quantity in tool_dict.items())).alias()
然后选择id位于交叉点的Toolbox
es:
In [10]: db.session.query(Toolbox).filter(Toolbox.id.in_(tools)).all()
Out[10]: [<__main__.Toolbox at 0x7f7ca781c048>]
In [11]: _[0].id
Out[11]: 1
从选择工具和CTE的别名形成CTE,因为我们必须在以后的内部NOT EXISTS中使用它:
In [41]: tools = db.union(*(db.session.query(Tool.toolbox_id, Tool.name).
...: filter(Tool.name == name,
...: Tool.quantity >= quantity)
...: for name, quantity in tool_dict.items())).cte()
In [42]: tools_alias = tools.alias()
形成所需工具的关系:
In [38]: required_tools = db.union(
...: *(db.select([db.literal(name).label('tool_name')])
...: for name in tool_dict.keys())).alias()
在某些数据库中,这可以更简单一些,例如在Postgresql中你可以这样做:
from sqlalchemy.dialects.postgresql import array
required_tools = func.unnest(array(list(tool_dict.keys()))).alias()
执行分裂:
In [63]: db.session.query(tools.c.tool_toolbox_id.distinct()).\
...: filter(~db.session.query().select_from(required_tools).
...: filter(~db.session.query().select_from(tools_alias).
...: filter(tools_alias.c.tool_toolbox_id == tools.c.tool_toolbox_id,
...: tools_alias.c.tool_name == required_tools.c.tool_name).
...: exists().correlate_except(tools_alias)).
...: exists()).all()
Out[63]: [(1)]
双重嵌套否定是一个眼睛,但它回答了查询“找到那些工具箱(ids),而这些工具箱中没有这样的必需工具”。
要直接获取所需的Toolbox
es,我们可以稍微调整一下查询,以便从顶层的toolbox
关系和最里层的形成的联合中进行选择:
In [16]: tools = db.union(*(db.session.query(Tool.toolbox_id, Tool.name).
...: filter(Tool.name == name,
...: Tool.quantity >= quantity)
...: for name, quantity in tool_dict.items())).alias()
In [17]: db.session.query(Toolbox).\
...: filter(~db.session.query().select_from(required_tools).
...: filter(~db.session.query().select_from(tools).
...: filter(tools.c.tool_toolbox_id == Toolbox.id,
...: tools.c.tool_name == required_tools.c.tool_name).
...: exists().correlate_except(tools)).
...: exists()).all()
Out[17]: [<__main__.Toolbox at 0x7f302f589898>]
In [18]: _[0].id
Out[18]: 1