2017-03-17 39 views
0

比方說,我有一個模型工具箱,用各種工具的外鍵關聯:過濾在瓶SQL-鍊金術中ForeignKey的多個實例

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把扳手的Toolboxes列表?

我有這麼遠:

t = Toolbox.query.join(Tool).filter(Tool.name.in_(tool_dict.keys())) 

但是這裏返回螺絲刀和扳手所有工具箱不論數量多少。

回答

0

解決這個問題的一種方法是首先從tool關係中選擇一組(toolbox_id, name)元組的集合,其中quantity滿足。然後divide發現設置了所需工具的名稱,並且您將剩下所需的toolbox_id

另外,在這種情況下,更簡單的方法是從滿足數量的工具中選擇工具箱ID的intersection,然後選擇與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() 

然後選擇Toolbox ES其中id是在路口:

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的別名,就像我們在內部使用它不遲存在:

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進行一些DB的簡單一點,例如,你可以這樣做:

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