SQLAlchemy ORM: Sum of products

坚强是说给别人听的谎言 提交于 2020-01-06 08:56:43

问题


Let's say I have a ROOMS table with width and length columns, and a corresponding SQLAlchemy model. Is there a clean and efficient way to get a total area of all rooms, i.e. sum(length x width)? It's easy enough to do by looping at the client but it must surely be more efficient if it can be fetched by a query at the server.

Edit:

I thought I was being helpful by reducing the problem to a simple, clean example but I now realise I was just shooting myself in the foot because my difficulties evidently stem from a more fundamental lack of understanding of SQLAlchemy and working with ORMs.

My model (flask-sqlalchemy) actually involves three related tables: holdings, commodities and prices. Commodities have many prices, and each holding is a quantity of a given commodity. I have it set up as follows:

class Holding(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    time = db.Column(db.TIMESTAMP, index=True)
    quantity = db.Column(db.DECIMAL(10,5))
    commodity_id = db.Column(db.Integer, db.ForeignKey('commodity.id'))

    commodity = db.relationship('Commodity', back_populates='holdings')

class Commodity(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    symbol = db.Column(db.String(20))
    name = db.Column(db.String(150))

    holdings = db.relationship('Holding', back_populates='commodity', lazy='dynamic')
    prices = db.relationship('Price', back_populates='commodity', lazy='dynamic', order_by='Price.time.desc()')

class Price(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    time = db.Column(db.TIMESTAMP, index=True)
    amount = db.Column(db.DECIMAL(10,5), index=True)
    commodity_id = db.Column(db.Integer, db.ForeignKey('commodity.id'))

    commodity = db.relationship('Commodity', back_populates='prices')

I want the sum of Holding.quantity * Holding.commodity.[most recent price].

Since Commodity.prices is in descending time order I can easily calculate the value in a Holding loop examining:

h.commodity.prices.first().amount * h.quantity

... but I can't see how to get at the related Price details from a single query, so I don't know how to apply @leovp's solution.

I hope that properly describes the problem now, apologies for the false start.


回答1:


The more interesting part about your question is solving the greatest-n-per-group problem. Now, I'm pretty green when it comes to MySQL, so there might be more efficient solutions to that than this:

In [43]: price_alias = db.aliased(Price)

In [44]: latest_price = db.session.query(Price.commodity_id, Price.amount).\
    ...:     outerjoin(price_alias,
    ...:               db.and_(price_alias.commodity_id == Price.commodity_id,
    ...:                       price_alias.time > Price.time)).\
    ...:     filter(price_alias.id == None).\
    ...:     subquery()

The self left join tries to join rows with greater time, which do not exist for the latest price, hence the filter(price_alias.id == None).

What's left then is to just join Holdings with the subquery:

In [46]: sum_of_products = db.session.query(
    ...:         db.func.sum(Holding.quantity * latest_price.c.amount)).\
    ...:     join(latest_price,
    ...:          latest_price.c.commodity_id == Holding.commodity_id).\
    ...:     scalar()



回答2:


Assuming your model is named Room and it has properties such as length and width:

from sqlalchemy import func
total_area = session.query(func.sum(Room.length * Room.width))

Which is going to be translated as something like that:

SELECT sum(rooms.length * rooms.width) AS sum_1 
FROM rooms


来源:https://stackoverflow.com/questions/47983152/sqlalchemy-orm-sum-of-products

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!