Django聚合:两个字段相乘的总和

66

我有一个类似下面的模型:

class Task(models.Model):
    progress = models.PositiveIntegerField()
    estimated_days = models.PositiveIntegerField()

现在我想在数据库层面上执行一个计算 Sum(progress * estimated_days)。使用Django聚合,我可以为每个字段获得总和,但无法获得字段乘积的总和。

4个回答

104

从Django 1.8及以上版本开始,您可以将表达式传递给聚合函数:

 from django.db.models import F

 Task.objects.aggregate(total=Sum(F('progress') * F('estimated_days')))['total']

还有常量可用,一切都可以组合:

 from django.db.models import Value

 Task.objects.aggregate(total=Sum('progress') / Value(10))['total']

6
我认为这个答案更易于理解和维护。另外,如果这些字段的类型不同,请在聚合函数中添加“output_field”参数。 - Neo Ko
1
我正在尝试在Django 1.7上运行它,但是我遇到了AttributeError AttributeError:'ExpressionNode'对象没有'split'属性的错误。我的Django版本是1.7.10。 - Grijesh Chauhan
如果progress是其他模型的外键,例如让它成为Product并且它有一个字段price,那么应该如何重新格式化它? Task.objects.aggregate(total=Sum(F('progress.price') * F('estimated_days')))['total']无法工作。 - ANFAS PV
这个解决方案在我的情况下适用...谢谢。 - Ajay Kumar

78

更新:对于 Django >= 1.8 版本,请参照 @kmmbvnr 提供的回答。

可以使用 Django ORM 实现:

下面是具体步骤:

from django.db.models import Sum

total = ( Task.objects
            .filter(your-filter-here)
            .aggregate(
                total=Sum('progress', field="progress*estimated_days")
             )['total']
         )

注意:如果两个字段是不同类型的,例如 integerfloat,你想要返回的类型应该作为 Sum 的第一个参数传递。
这是一个晚回答,但我想它会帮助到寻找同样答案的人。

2
很好用,谢谢。但要注意的是,field kwarg未记录在文档中,在Django测试套件中也没有找到任何有关它的测试。 - Sébastien Trottier
“progress”字段有什么作用?我正在尝试弄清楚这段代码片段,因为它正是我所需要的。 - Maor
1
@Maor 我想你是指 Sum 的第一个参数吧?那么,正如我所提到的,如果这两个字段是不同类型的,比如整数和浮点数,你想要返回的类型应该作为 Sum 的第一个参数传递。 - sha256
1
@sha256,我猜你的意思是Sum函数的第一个参数应该是你想要结果返回类型的字段名称?在这种情况下,结果将以progress字段的数据类型返回? - Binoj D
@BinojDavid,没错! - sha256

37

解决方案取决于Django版本。

  • django < 1.8

    from django.db.models import Sum
    MyModel.objects.filter(<filters>).aggregate(Sum('field1', field="field1*field2"))
    
  • django >= 1.8

    from django.db.models import Sum, F
    MyModel.objects.filter(<filters>).aggregate(Sum(F('field1')*F('field2')))
    

当使用不同类型字段进行聚合时,您需要使用ExpressionWrapper。请查看以下解决方案:https://stackoverflow.com/questions/55458958/how-to-aggregate-the-average-of-a-calculation-based-on-two-columns?answertab=votes#tab-top - serfer2

2

在Django 1.8版本之后

自Django 1.8版本开始,您可以使用F

from django.db.models import F. Sum

total = ( 
    Task
    .objects
    .aggregate(total=Sum(F('progress') * F('estimated_days'))) 
    ['total']
)

旧答案(Django 1.8之前)

本答案写于2012年,Django 1.8于2015年发布。

你有几个选项:

  1. 原始查询
  2. Emulbreh的未记录方法
  3. 创建第三个字段progress_X_estimated_days并在保存覆盖方法中更新它。然后通过这个新字段进行聚合。

覆盖:

class Task(models.Model):
   progress = models.PositiveIntegerField()
   estimated_days = models.PositiveIntegerField()
   progress_X_estimated_days = models.PositiveIntegerField(editable=False)

   def save(self, *args, **kwargs):
      progress_X_estimated_days = self.progress * self.estimated_days
      super(Task, self).save(*args, **kwargs)

是的,我其实把原始SQL或额外的属性作为最后的选择。无论如何还是谢谢你。 - Raunak Agarwal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接