【问题标题】:Annotate a sum of two fields multiplied注释两个字段相乘的总和
【发布时间】:2023-03-16 08:20:01
【问题描述】:

我有三个模型,以简化为例:

class Customer(models.Model):
    email = models.CharField(max_length=128)

class Order(models.Model):
    customer = models.ForeignKey(Customer)
    order_status = models.CharField(blank=True, max_length=256)

class Lineitem(models.Model):
    order = models.ForeignKey(Order)
    quantity = models.IntegerField(blank=True)
    price = models.DecimalField(max_digits=6, decimal_places=2)

我想查询客户(可能使用过滤器)并注释他们花费的总额(即总和超过(价格 * 数量)

我试过了:
Customer.objects.filter(something).annotate(total_spent=Sum(F('order__lineitem__quantity') * F('order__lineitem__price')))

看起来 Sum() 不能与 F() 表达式一起使用。有没有其他方法可以做到这一点?

【问题讨论】:

标签: django django-models


【解决方案1】:

也许您现在不需要这个答案,但是如果您阅读了有关 Sum expression 的文档,则需要声明 output_field,如下所示:

Customer.objects.filter(something)
                .annotate(total_spent=Sum(
                    F('order__lineitem__quantity') * 
                    F('order__lineitem__price'),   
                    output_field=models.FloatField()
                ))

【讨论】:

    【解决方案2】:

    您是否考虑过使用.extra() 方法?

    请参阅Django QuerySet API

    【讨论】:

    • 我有。它有效,但我试图避免它有两个原因:首先,它使用每行子查询而不是连接,这对于某些数据库后端可能会严重扩展。其次,它不适用于额外字段上的 filter(),因此它不能在程序上与其他 Q 对象组合
    【解决方案3】:

    您可以尝试在LineItem 模型中使用属性:

    class Lineitem(models.Model):
        order = models.ForeignKey(Order)
        quantity = models.IntegerField(blank=True)
        price = models.DecimalField(max_digits=6, decimal_places=2)
        def _get_total(self):
            return quantity * price
        total = property(_get_total)
    

    那我想你可以用使用的总金额来注释

    Customer.objects.filter(something).annotate(total_spent=Sum('order__lineitem__total'))
    

    我不知道这种方法的效率与其他方法有什么关系,但它比另一种方法更Pythonic/Django-y,即像中一样手动编写整个SQL查询

    Customer.objects.raw("SELECT ... from <customer_table_name> where ...")
    

    【讨论】:

    • 我已经尝试过了,但它不起作用,我收到了 Cannot resolve keyword into field 错误。 :-(
    • 好的,从另一个答案的评论看来,这不能通过对属性进行注释来完成。
    • Django ORM 生成通过 SQL 执行计算的查询,因此,不可能在方法中包含 Python 定义的属性作为查询的一部分。您可以应用列表理解后查询,但这不是一回事,正如 Justin Hamade 指出的那样。
    【解决方案4】:

    您可能需要推出自己的自定义聚合器。您可以找到一个简单的 GROUP_CONCAT 示例,它应该可以帮助您从这里开始: http://harkablog.com/inside-the-django-orm-aggregates.html

    【讨论】:

      【解决方案5】:

      我刚刚遇到了这个问题,我认为该注释不会与属性一起使用,请参阅Django - Can you use property as the field in an aggregation function?

      这就是我所做的。

      class Customer(models.Model):
          email = models.CharField(max_length=128)
      
      class Order(models.Model):
          customer = models.ForeignKey(Customer)
          order_status = models.CharField(blank=True, max_length=256)
      
      class Lineitem(models.Model):
          order = models.ForeignKey(Order)
          quantity = models.IntegerField(blank=True)
          price = models.DecimalField(max_digits=6, decimal_places=2)
          @property
          def total(self):
              return self.quantity * self.price
      

      然后使用 sum 和列表推导:

      sum([li.total for li in  LineItem.objects.filter(order__customer=some_customer).filter(somefilter)])
      

      【讨论】:

      • 这不起作用,因为属性是在查询之后计算的
      • 此选项有效,但您在 python 中而不是 sql 中进行求和,这意味着它变大时会慢很多。
      【解决方案6】:

      类似于:https://stackoverflow.com/a/19888120/1344647

      from django.db.models import Sum
      
      q = Task.objects.filter(your-filter-here).annotate(total=Sum('progress', field="progress*estimated_days"))
      

      已编辑:感谢@Max,使用注释而不是聚合。

      【讨论】:

      • 你以正确的方式:但不是聚合使用注释和删除 ['total'] 结果:total = Task.objects.filter(your-filter-here).annotate(total=Sum ('进度',字段=“进度*估计天数”))。将示例重新制作为积极的
      猜你喜欢
      • 1970-01-01
      • 2012-08-23
      • 1970-01-01
      • 1970-01-01
      • 2017-12-25
      • 2014-11-09
      • 2021-10-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多