【问题标题】:SQLAlchemy ORM: Sum of productsSQLAlchemy ORM:产品总和
【发布时间】:2017-12-26 19:55:29
【问题描述】:

假设我有一个包含宽度和长度列的 ROOMS 表,以及一个相应的 SQLAlchemy 模型。有没有一种干净有效的方法来获得所有房间的总面积,即总和(长 x 宽)?通过在客户端循环很容易做到这一点,但如果可以通过服务器上的查询获取它肯定会更有效。

编辑:

我认为我可以通过将问题简化为一个简单、干净的示例来提供帮助,但我现在意识到我只是在踢自己的脚,因为我的困难显然源于对 SQLAlchemy 和使用 ORM 的更根本的理解不足。

我的模型(flask-sqlalchemy)实际上涉及三个相关的表:holdings、commercial 和 prices。商品有多种价格,每份持有量都是给定商品的数量。我的设置如下:

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

我想要 Holding.quantity * Holding.commodity.[最新价格] 的总和。

由于 Commodity.prices 是按时间降序排列的,我可以很容易地计算出持有循环检查中的值:

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

...但是我看不到如何从单个查询中获取相关的价格详细信息,所以我不知道如何应用@leovp 的解决方案。

我希望现在正确描述问题,为错误的开始道歉。

【问题讨论】:

  • 假设,假设......那么你有没有模特?!是的,这是可能的。只需选择产品的总和即可。
  • 不,我没有那个特定的模型 - ROOMS 示例是一个简化,我认为它很好地描述了问题。显然我不知道如何选择产品的总和。
  • @Ilja Everilä 我可以在原始 SQL 中执行此操作,但我想知道如何使用 SQLAlchemy ORM 方式执行此操作。希望我更新的描述能澄清。
  • 你用的是什么数据库?
  • MySQL(5.7 版)。

标签: python orm sqlalchemy greatest-n-per-group


【解决方案1】:

关于你的问题更有趣的部分是解决 问题。现在,我对 MySQL 很陌生,所以可能有比这更有效的解决方案:

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

self left join 尝试加入 时间 更长的行,这些行对于最新价格不存在,因此filter(price_alias.id == None)

剩下的就是将Holdings 加入子查询:

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

【讨论】:

  • 谢谢,效果很好。自联接方法是我目前在原始 SQL 的其他地方使用的方法。除了几年前的快速浏览之外,我是 SQLAlchemy(和 Python 相关的)新手,我一直在纠结,试图用 ORM 进行简单查询之外的任何事情。您的解决方案比原始 SQL 慢一点,但这是可以预料到的库开销,它仍然只有几分之一秒,这已经足够快了。我必须在仔细阅读 SQLAlchemy 文档的同时对其进行剖析,以更好地了解它的工作原理!
  • 如果您更熟悉原始 SQL,您有时可能会从 Query.from_statement() 中受益——如果您想结合原始 SQL 和查询 ORM 实体。当然,不时有Session.execute() 用于那个奇怪的查询。 SQLAlchemy 的特点是它有层次。 ORM 构建在 Core 之上,它使用 DB-API 连接等。
  • Session.execute() 是我到目前为止所使用的,我将看看 Query.from_statement() - 感谢您的提示。我对 SQL 更熟悉(虽然不完全是专家),但我想了解更多关于 ORM 的信息。如果我留在自己的舒适区,一旦事情变得有点复杂,就直接使用 SQL 就不会发生这种情况。
【解决方案2】:

假设您的模型名为 Room,并且具有 lengthwidth 等属性:

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

这将被翻译成这样的:

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

【讨论】:

  • 谢谢,但我意识到我比我想象的要努力 - 请参阅编辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-30
  • 1970-01-01
  • 2023-03-09
  • 1970-01-01
  • 1970-01-01
  • 2021-06-17
相关资源
最近更新 更多