使用Django ORM和PostgreSQL进行累计(运行)总和

11

使用Django的ORM能否计算累积(运行)总和?考虑以下模型:

class AModel(models.Model):
    a_number = models.IntegerField()

给定一组数据,其中a_number = 1。假设数据库中有一个数字大于1的AModel实例,它们都满足a_number=1。我希望能够返回以下内容:

AModel.objects.annotate(cumsum=??).values('id', 'cumsum').order_by('id')
>>> ({id: 1, cumsum: 1}, {id: 2, cumsum: 2}, ... {id: N, cumsum: N})

理想情况下,我希望能够限制/过滤累加和。因此,在上面的例子中,我希望将结果限制为 cumsum <= 2

我相信在postgresql中可以使用窗口函数实现累加和。如何将其转换为ORM?


cumsum表示累加和,显然这是针对多个记录的,编辑以使数据集的大小大于1更清晰。 - wrdeman
我认为你不能使用ORM完成它...最好使用Python。 - Mihai Zamfir
你要找的短语是“running total”(或称为running sum)。这是移动聚合的一种特殊情况。而移动聚合则是窗口函数的一种。 - pozs
@wrdeman 是的,你可能是对的,但术语“累积和”很少使用(我主要发现MATLAB参考和这个worfram)。虽然我不是以英语为母语的人,但我之前听说过“运行总和”的说法,而且听得很多。 - pozs
它的使用可能更加领域特定(例如物理/统计学,在这些领域中并不罕见),但感谢您指出这一点,我已经修改了标题以反映这种替代用法。 - wrdeman
显示剩余2条评论
5个回答

16

参考资料,从Django 2.0开始,可以使用Window函数来实现此结果:

AModel.objects.annotate(cumsum=Window(Sum('a_number'), order_by=F('id').asc()))\
              .values('id', 'cumsum').order_by('id', 'cumsum')

1
有趣的是,窗口函数在SqLite3中不起作用。 - Campi

5

根据Dima Kudosh的回答和https://dev59.com/ZW025IYBdhLWcg3w9qyQ#5700744,我需要执行以下操作: 我在SQL中删除了对PARTITION BY的引用,并替换为ORDER BY,结果如下。

AModel.objects.annotate(
    cumsum=Func(
        Sum('a_number'), 
        template='%(expressions)s OVER (ORDER BY %(order_by)s)', 
        order_by="id"
    ) 
).values('id', 'cumsum').order_by('id', 'cumsum')

这将生成以下的SQL语句:
SELECT "amodel"."id",
SUM("amodel"."a_number") 
OVER (ORDER BY id) AS "cumsum" 
FROM "amodel" 
GROUP BY "amodel"."id" 
ORDER BY "amodel"."id" ASC, "cumsum" ASC

迪玛库多斯的答案没有将结果加总,但上述内容可以做到。

2

为了后代,我发现这对我是一个很好的解决方案。我不需要结果是一个QuerySet,所以我可以负担得起这样做,因为我只是要使用D3.js绘制数据:

import numpy as np
import datettime

today = datetime.datetime.date()

raw_data = MyModel.objects.filter('date'=today).values_list('a_number', flat=True)

cumsum = np.cumsum(raw_data)

1
你可以尝试使用函数表达式来实现这个。
from django.db.models import Func, Sum

AModel.objects.annotate(cumsum=Func(Sum('a_number'), template='%(expressions)s OVER (PARTITION BY %(partition_by)s)', partition_by='id')).values('id', 'cumsum').order_by('id')

谢谢,非常感谢你的回答。对我来说并没有完全起作用,我已经发布了我的修改。 - wrdeman

0

请检查这个

AModel.objects.order_by("id").extra(select={"cumsum":'SELECT SUM(m.a_number) FROM table_name m WHERE m.id <= table_name.id'}).values('id', 'cumsum')

table_name 应该是数据库中表的名称。


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