【问题标题】:SQLAlchemy reference a subquery from a case expressionSQLAlchemy 从 case 表达式中引用子查询
【发布时间】:2018-04-11 21:36:46
【问题描述】:

我有一个 hybrid_property,它根据一对多关系上的一些计算返回一个字符串。

hybrid_property 表达式的原始 sql 是: 这是原始的 sql:

SELECT
  CASE
    WHEN s.quantity_received = 0 THEN "unreceived"
    WHEN s.dif = 0.0  THEN "received"
    WHEN s.dif > 0.0  THEN "partially_received"
    WHEN s.dif < 0.0  THEN "over_received"
  END as status
FROM (
      SELECT li.quantity_received, sum(li.quantity - li.received) as 'dif'
      FROM line_items as li
      WHERE li.o_id = xxx
) as s

模型

class LineItem(BaseModel):

    __table__ = Table('line_items', autoload=True)

   order = relationship("Order", backreef="line_itms", primaryjoin="Order.id == foregin(LineItem.o_id)")


class Order(BaseModel):

    __table__ = Table('orders', autoload=True)

    @hybrid_property
    def status(self):
        qty_received, qty_ordered = 0, 0

        for li in self.line_items:
            if li.status != "cancelled":
                qty_ordered += li.quantity
                qty_received += li.quantity_received

        if qty_received == 0:
            status = "unreceived"

        elif qty_received == qty_ordered:
            status = "received"

        elif qty_received < qty_ordered:
            status = "partially_received"

        elif qty_received > qty_ordered:
            status = "over_received"

        return status


    @status.expression
    def status(cls):
        line_items_calc = select([LineItem.quantity_received,
                    func.sum(LineItem.quantity - LineItem.quantity_received).label('dif')]) \
            .where(and_(LineItem.o_id == Order.id,
                   or_(LineItem.fulfillment_status != "cancelled",
                       LineItem.fulfillment_status == None))) \
            .alias()

        qq = select([
            case([
                 (qs.c.quantity_received == 0, "unreceived"),
                 (qs.c.dif == 0, "received"),
                 (qs.c.dif > 0, "partially_received"),
                 (qs.c.dif < 0, "over_received")]
             )]) \
             .select_from(line_items_calc) \
             .as_scalar()

        return qq

我有 2 个订单,o1 和 o2 以及订单项:

  LineItem(o_id=o1.id, quantity=1, quantity_received=1)
  LineItem(o_id=o2.id, quantity=1, quantity_received=0)
  LineItem(o_id=o2.id, quantity=2, quantity_received=1)

Order1 的状态应为“已收到”,而 Order2 的状态应为“partially_received”。

但是当我查询“received”时,我什么也得不到,当查询“partially_received”时,我得到 2 个结果而不是 1 个。

看起来它没有按 Order.id 过滤 LineItems,因此它使用 all 来计算状态(因为 total_qty 为 4,total received 为 2,这将给出“partially_received”)

Order.query().filter(Order.status == 'received').all()  # returns []
Order.query().filter(Order.status == 'partially_received').all()  # returns [Order1, Order2]

如果将 .correlate_except(LineItem) 添加到 line_items_calc 查询,我会收到以下错误:

OperationalError: (_mysql_exceptions.OperationalError) (1054, “'where 子句'中的未知列'orders.id'”)[SQL:u'SELECT count(*) AS count_1 \nFROM (SELECT * \nFROM orders \nWHERE orders.account_id = %s AND (SELECT CASE WHEN (a_3.quantity_received = %s) THEN %s WHEN (a_3.dif = %s) THEN %s WHEN (a_3.dif > %s) THEN %s WHEN (a_3.dif

【问题讨论】:

  • @IljaEverilä 是的!出于某种原因,如果我手动执行最后一个选择,它会返回正确的值,但是当用作过滤器时返回空。
  • 所以我得到了部分工作,因为不是通过 cls.id 过滤得到每个 A 的每个 P,因此状态字符串错误。
  • @IljaEverilä 感谢您迄今为止的帮助。我已更新问题以提供有关模型和问题的更好信息。如果我尝试使用 correlate_except() 我得到一个错误

标签: python sqlalchemy


【解决方案1】:

您似乎正在尝试将表达式与最外层查询相关联,但事实证明,当前的嵌套子查询方法在 MySQL 中是不可行的,因为它does not allow correlated subqueries in FROM clause 与其他一些数据库相比只是不允许与之前的 FROM 列表项关联,除非使用 LATERAL。

另一方面,嵌套子查询是多余的,因为您可以在 SELECT 列表中的 CASE 表达式中使用聚合,但在当前的子查询中,您混合了非聚合和聚合表达式:

SELECT li.quantity_received, sum(li.quantity - li.received) as 'dif'

这很可能不是您想要的。其他一些数据库甚至不允许执行这样的查询,但是如果禁用了ONLY_FULL_GROUP_BY,MySQL 会从组中未指定的行中默默地为li.quantity_received 选择一个值。它在 5.7.5 及更高版本中默认启用,您应该考虑启用它。查看您的混合资产的另一半,您可能还打算计算收到数量的总和。

以下是满足您在问题中提出的 2 个测试用例的状态表达式版本:

 @status.expression
 def status(cls):
     qty_received = func.coalesce(func.sum(LineItem.quantity_received), 0)
     qty_ordered = func.coalesce(func.sum(LineItem.quantity), 0)

     return select([case([
             (qty_received == 0, "unreceived"),
             (qty_received == qty_ordered, "received"),
             (qty_received < qty_ordered, "partially_received"),
             (qty_received > qty_ordered, "over_received")])]).\
         where(and_(func.coalesce(LineItem.fulfillment_status, "") != "cancelled",
                    LineItem.o_id == cls.id)).\
         correlate_except(LineItem).\
         as_scalar()

我相信它比您的原始方法更接近 Python 方面的方法。注意使用 COALESCE 处理 NULL。

【讨论】:

  • 你太棒了!是的,我昨天意识到非聚合错误。非常感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-06
  • 2011-05-05
  • 1970-01-01
  • 1970-01-01
  • 2015-09-10
相关资源
最近更新 更多