猿问

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

我有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)

这非常快,但过滤很糟糕,因为我没有过滤最后的操作,而是这里的所有操作。这是错误的。


还有其他想法吗?谢谢。


胡说叔叔
浏览 122回答 1
1回答

隔江千里

使用 annotate() 方法好像是不可能的。当然,这是可能的 ;) 您可以使用子查询或一些巧妙的条件表达式。假设您想从上次订单操作中获取总金额,以下是子查询示例:from django.db.models import Subquery, OuterReforders = Order.objects.annotate(&nbsp; &nbsp; total=Subquery(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# [1]&nbsp; &nbsp; &nbsp; &nbsp; OrderOperation.objects \&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .filter(order_id=OuterRef("pk")) \&nbsp; # [2]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .order_by('-id') \&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # [3]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .values('total') \&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # [4]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [:1]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # [5]&nbsp; &nbsp; ))上面代码的解释:我们正在向结果列表添加新字段,称为totaltaht 将由子查询填充。您可以Order将此查询集中的任何其他模型字段访问它(在评估它之后,在模型实例中或在过滤和其他注释中)。您可以从Django 文档中了解注释的工作原理。子查询应该只针对当前订单的操作调用。OuterRefjust 将被替换为对结果 SQL 查询中选定字段的引用。我们想按操作id降序排序,因为我们确实想要最新的。如果您的操作中有其他字段需要按顺序排序(例如创建日期),请在此处填写。该子查询应该只total从操作返回值我们只想要一个元素。它是使用切片符号而不是普通索引来获取的,因为在 django 查询集上使用索引将立即调用它。切片只是LIMIT向 SQL 查询添加子句,而不调用它,这就是我们想要的。现在您可以使用:orders.filter(total__lte=some_value)只获取您想要的订单。您还可以使用该注释来
随时随地看视频慕课网APP

相关分类

Python
我要回答