在Flask SQL-Alchemy中使用ForeignKey中的多个实例进行过滤

时间:2017-03-17 13:13:17

标签: python flask sqlalchemy

我们说我有一个模型工具箱,与各种工具相关的外键:

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

然而,无论数量多少,都会返回包含螺丝刀和扳手的所有工具箱。

1 个答案:

答案 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