为了账号安全,请及时绑定邮箱和手机立即绑定

Django:使用传播数据优化查询

Django:使用传播数据优化查询

胡说叔叔 2021-11-16 15:25:48
我有Order对象和OrderOperation对象代表对订单的操作(创建、修改、取消)。从概念上讲,一个订单有一对多的订单操作。每次对订单进行操作时,都会在此操作中计算总数。这意味着当我需要找到一个订单的总数时,我只得到最后一个订单操作的总数。简化的代码class OrderOperation(models.Model):    order = models.ForeignKey(Order)    total = DecimalField(max_digits=9, decimal_places=2)class Order(models.Model):    @property    def last_operation(self) -> Optional['OrderOperation']:        try:            qs = self.orderoperation_set.all()            return qs[len(qs) - 1]        except AssertionError:  # when there is a negative indexing (no operation)            # IndexError can not happen            return None    @property    def total(self) -> Optional[Decimal]:        last_operation = self.last_operation        return last_operation.total if last_operation else None问题由于接到的订单很多,每次想做“总低于5欧元的订单”之类的简单过滤,需要很长时间,因为要浏览所有订单,使用如下,明显不好查询:all_objects = Order.objects.all()Order.objects.prefetch_related('orderoperation_set').filter(    pk__in=[o.pk for o in all_objects if o.total <= some_value])我目前的想法/我尝试过的数据非规范化?我可以简单地创建一个total属性 on Order,并在每次创建操作时将操作总数复制到订单总数中。然后,Order.objects.filter(total__lte=some_value)会工作。但是,在我的数据库中复制数据之前,我想确保没有更简单/更清洁的解决方案。使用 annotate() 方法?不知何故,我希望能够做到:Order.objects.annotate(total=something_magical_here).filter(total__lte=some_value)。好像是不可能的。单独过滤然后匹配?order_operations = OrderOperation.objects.filter(total__lte=some_value)orders = Order.objects.filter(orderoperation__in=order_operations)这非常快,但过滤很糟糕,因为我没有过滤最后的操作,而是这里的所有操作。这是错误的。还有其他想法吗?谢谢。
查看完整描述

1 回答

?
隔江千里

TA贡献1906条经验 获得超10个赞

使用 annotate() 方法

好像是不可能的。


当然,这是可能的 ;) 您可以使用子查询或一些巧妙的条件表达式。假设您想从上次订单操作中获取总金额,以下是子查询示例:


from django.db.models import Subquery, OuterRef


orders = Order.objects.annotate(

    total=Subquery(                             # [1]

        OrderOperation.objects \

            .filter(order_id=OuterRef("pk")) \  # [2]

            .order_by('-id') \                  # [3]

            .values('total') \                  # [4]

            [:1]                                # [5]

    )

)

上面代码的解释:

  1. 我们正在向结果列表添加新字段,称为totaltaht 将由子查询填充。您可以Order将此查询集中的任何其他模型字段访问它(在评估它之后,在模型实例中或在过滤和其他注释中)。您可以从Django 文档中了解注释的工作原理。

  2. 子查询应该只针对当前订单的操作调用。OuterRefjust 将被替换为对结果 SQL 查询中选定字段的引用。

  3. 我们想按操作id降序排序,因为我们确实想要最新的。如果您的操作中有其他字段需要按顺序排序(例如创建日期),请在此处填写。

  4. 该子查询应该只total从操作返回值

  5. 我们只想要一个元素。它是使用切片符号而不是普通索引来获取的,因为在 django 查询集上使用索引将立即调用它。切片只是LIMIT向 SQL 查询添加子句,而不调用它,这就是我们想要的。

现在您可以使用:

orders.filter(total__lte=some_value)

只获取您想要的订单。您还可以使用该注释来


查看完整回答
反对 回复 2021-11-16
  • 1 回答
  • 0 关注
  • 121 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信