最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - How to add Flask sqlalchemy multiple tables query result with column name & value into a list (to use with json

programmeradmin5浏览0评论

I have got 2 tables, one is department and another one is supplier. For every department, there could be a supplier or it could be none. So I use the outerjoin and the result is correct. I need to know how add these results in to a list

@department.route("/department/getDepartmentDetails/<int:id>", methods=['GET', 'POST'])
def getDepartmentDetails(id):
     department = db.session.query(Department, Supplier) \
        .outerjoin(Department, Department.default_supplier_id == Supplier.id) \
        .filter(Department.id == id)

If I query single table like below, I can use getattr to get the desired result

department = Department.query.filter(Department.id==id).first()
dept={c.name: str(getattr(department, c.name)) for c in department.__table__.columns}
return jsonify(dept)

How do I get the same for the multi-table query result?

I have got 2 tables, one is department and another one is supplier. For every department, there could be a supplier or it could be none. So I use the outerjoin and the result is correct. I need to know how add these results in to a list

@department.route("/department/getDepartmentDetails/<int:id>", methods=['GET', 'POST'])
def getDepartmentDetails(id):
     department = db.session.query(Department, Supplier) \
        .outerjoin(Department, Department.default_supplier_id == Supplier.id) \
        .filter(Department.id == id)

If I query single table like below, I can use getattr to get the desired result

department = Department.query.filter(Department.id==id).first()
dept={c.name: str(getattr(department, c.name)) for c in department.__table__.columns}
return jsonify(dept)

How do I get the same for the multi-table query result?

Share Improve this question asked Feb 7 at 16:56 Michael ValanMichael Valan 213 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

Assuming that you want to return a single dict (or JSON object) for each row in the resultset, all that is required is generate a dict for each entity in each (Department, Supplier) row, and merge them. However we need to consider that the models may share some attribute names, which must be disambiguated.

The disambiguation can be handled by creating a function, based on your code, which accepts a prefix string and an entity, and returns a dict of prefixed attribute names and values:

def to_prefix_dict(prefix, entity):
    if not entity:
        return {}
    return {
        f'{prefix.lower()}_{c.name}': getattr(entity, c.name) for c in entity.__table__.columns
    }

It can be used like this:

import functools
import sqlalchemy as sa
...

# This code is vanilla SQLAlchemy, but Flask-SQLAlchemy code will be 
# essentially the same: substitute `db.session.query` for `sa.select` and
# `db.session` for `s`.
with Session() as s:
    q = sa.select(Department, Supplier).outerjoin(
        Supplier, Department.default_supplier_id == Supplier.id
    )
    rs = s.execute(q)
    result = [
        functools.reduce(
            dict.__or__,
            [to_prefix_dict(key, entity) for key, entity in zip(rs.keys(), row)],
        )
        for row in rs
    ]

Resulting in a structure like this when passed through jsonify, assuming one department with a supplier, and one without:

[
  {
    "department_id": 1,
    "department_name": "D1",
    "department_default_supplier_id": 1,
    "supplier_id": 1,
    "supplier_name": "S1"
  },
  {
    "department_id": 2,
    "department_name": "S2",
    "department_default_supplier_id": null
  }
]

Since the ORM entities returned by the query aren't really required for the output, you could consider specifying the required columns and their labels directly:

    q = sa.select(
        Department.name.label('department'), Supplier.name.label('supplier')
    ).outerjoin(Supplier, Department.default_supplier_id == Supplier.id)
    rs = s.execute(q)
    result = [dict(r) for r in rs.mappings()]

Output:

[
  {
    "department": "D1",
    "supplier": "S1"
  },
  {
    "department": "S2",
    "supplier": null
  }
]

Manual labelling can be avoided by configuring the query to automatically label each column as <table name + column name>. If your table names are not siutable for exposure you can use an alias:

    dept = orm.aliased(Department, name='dep')
    supp = orm.aliased(Supplier, name='sup')
    q = (
        sa.select(dept.name, supp.name)
        .outerjoin(supp, dept.default_supplier_id == supp.id)
        .set_label_style(sa.LABEL_STYLE_TABLENAME_PLUS_COL)
    )
    rs = s.execute(q)
    result = [dict(r) for r in rs.mappings()]

Output:

[
  {
    "department": "D1",
    "supplier": "S1"
  },
  {
    "department": "S2",
    "supplier": null
  }
]

If all the column in both tables are required, you can unpack both tables' columns attributes.

    dept = sa.alias(Department.__table__, 'dep')
    supp = sa.alias(Supplier.__table__, 'sup')
    q = (
        sa.select(*dept.columns, *supp.columns)
        .outerjoin(supp, dept.c.default_supplier_id == supp.c.id)
        .set_label_style(sa.LABEL_STYLE_TABLENAME_PLUS_COL)
    )
    rs = s.execute(q)
    result = [dict(r) for r in rs.mappings()]

Output:

[
  {
    "dep_id": 1,
    "dep_name": "D1",
    "dep_default_supplier_id": 1,
    "sup_id": 1,
    "sup_name": "S1"
  },
  {
    "dep_id": 2,
    "dep_name": "S2",
    "dep_default_supplier_id": null,
    "sup_id": null,
    "sup_name": null
  }
]

Alternatively, this function can used to return a nested row structure, which may be useful if the consumer of the JSON needs to handle departments and suppliers separately:

def to_dict(entity):
    if not entity:
        return {}
    return {c.name: getattr(entity, c.name) for c in entity.__table__.columns}

like this:

...
    result = [
        {key_name: to_dict(entity) for key_name, entity in zip(rs.keys(), row)}
        for row in rs
    ]

With this result:

[                   
  { 
    "Department": {
      "id": 1,         
      "name": "D1", 
      "default_supplier_id": 1
    },
    "Supplier": {                                                                                        
      "id": 1,                                                                                           
      "name": "S1"                                                                                       
    }
  },
  {                  
    "Department": { 
      "id": 2,
      "name": "S2",
      "default_supplier_id": null
    },              
    "Supplier": {}
  }
] 

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论