SQLAlchemy 查询将相关数据的伪列添加为字典数组

SQLAlchemy query adding pseudo-column of related data as an array of dicts

以下内容与用 python 编写并使用 SQLAlchemy 连接到 postgres (v9.5) 关系数据库的 Web 应用程序后端有关。

假设我们有两个 table:itemsrelations。每个项目都有一个 ID,并且可能与多个其他项目相关。这样的关系保存在关系 table 中,它存储自己的 ID、两个相关项目的 ID 加上关系的名称。

这是一些示例数据:

     items:                               relations:

  id      name                id     item1_id  item2_id    name
---------------              ----------------------------------
  1       car                  1        1         2       weight
  2       boat                 2        1         3       colour
  3       bike                 3        1         4        age
  4       jet-pack             4        5         1        age
  5       plane
  6       rocket          

因此,例如 carbike 通过颜色相关。

我正在寻找一个 SQLAlchemy 代码摘录,用于对 return 所有 项目 的数据库查询以及一个额外的 derived- 伪列 包含每个元组的关系数据作为结构数组:

 id       name                                 relations
---------------------------------------------------------------------------------
  1       car            [ { 2, weight }, { 3, colour }, { 4, age }, { 5, age } ]
  2       boat           [ { 1, weight } ]
  3       bike           [ { 1, colour } ]
  4       jet-pack       [ { 1, age } ]
  5       plane          [ { 1, age } ]
  6       rocket         []

一个好的第一步是在尝试转换为 SQLAlchemy 之前生成 SQL 查询。我取得的最接近上述结果:

SELECT i.*, 
  (SELECT array_agg(row(CASE WHEN r.item1_id=i.id THEN r.item2_id ELSE r.item1_id END, i.name)) 
   AS relations from relations r 
   WHERE r.item1_id=i.id OR r.item2_id=i.id) 
FROM items i ;

但更好一点的是实际输出关系 json:

SELECT i.*,
  (SELECT jsonb_agg(jsonb_build_object('item', CASE WHEN r.item1_id=i.id THEN r.item2_id ELSE r.item1_id END, 'relation', r.name))     
     AS relations
     FROM relations r   
     WHERE r.item1_id=i.id OR r.item2_id=i.id)
  FROM items i;

产生:

 id       name                                 relations
---------------------------------------------------------------------------------
  1       car            [ { "item": 2, "relation": "weight" }, { "item": 3, "relation": "colour" }, { "item": 4, "relation": "age" }, { "item": 5, "relation": "age" } ]
  2       boat           [ { "item": 1, "relation": "weight" } ]
  3       bike           [ { "item": 1, "relation": "colour" } ]
  4       jet-pack       [ { "item": 1, "relation": "age" } ]
  5       plane          [ { "item": 1, "relation": "age" } ]
  6       rocket         []

这个子查询在sqlalchemy中是怎么实现的?

经过长时间浏览 SQLalchemy 文档和许多 web-sites,我在此处找到了基于相关 sub-query 的解决方案:

- 答案 #1。

按如下方式调整它会产生我要找的东西:

from sqlalchemy import select, func, table, Column, Integer, Text, case
from sqlalchemy.sql.expression import or_


items     = table('items',     Column('id', Integer), 
                               Column('name', Text))

relations = table('relations', Column('id', Integer), 
                               Column('item1_id', Integer), 
                               Column('item2_id', Integer), 
                               Column('name', Text))


subquery = select( [ func.jsonb_agg(
                       func.jsonb_build_object(
                         'item', case( [ ( relations.c.item1_id == items.c.id, relations.c.item2_id ) ],
                                       else_ = relations.c.item1_id),
                         'relation', relations.c.name
                       )
                     )
                   ]
                 ).where(or_(relations.c.item1_id == items.c.id, relations.c.item2_id == items.c.id) \
                 .correlate(items)

query = (
    select([items.*, subquery.label('relations')]).select_from(items)
)