You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django如何通过单查询实现按日期求和并添加7日滚动平均值?

Django日期分组求和+7天滚动平均值实现方案

问题背景

需要对Sales模型执行单次查询,完成两个需求:

  • 按日期分组,计算每日Value字段的总和
  • 为每个日期添加**过去7天(含当天)**的滚动平均值

示例数据表:

|---------------------|------------------|--------------|
|     Date            |     Category     |     Value    |
|---------------------|------------------|--------------|
|      2020-04-01     |         1        |      55.0    |
|---------------------|------------------|--------------|
|      2020-04-01     |         2        |      30.0    |
|---------------------|------------------|--------------|
|      2020-04-02     |         1        |      25.0    |
|---------------------|------------------|--------------|
|      2020-04-02     |         2        |      85.0    |
|---------------------|------------------|--------------|
|      2020-04-03     |         1        |      60.0    |
|---------------------|------------------|--------------|
|      2020-04-03     |         2        |      30.0    |
|---------------------|------------------|--------------|

原尝试代码:

days = (
        Sales.objects.values('date').annotate(sum_for_date=Sum('value'))
        ).annotate(
                rolling_avg=Window(
                         expression=Avg('sum_for_date'),
                         frame=RowRange(start=-7,end=0),
                         order_by=F('date').asc(),
                    )
                )
        .order_by('date')

抛出错误:

django.core.exceptions.FieldError: Cannot compute Avg('sum_for_date'): 'sum_for_date' is an aggregate

错误原因

Django的Window函数无法直接引用同一查询中通过annotate生成的聚合字段(如sum_for_date)。这是因为窗口函数的计算逻辑和普通聚合处于查询的不同执行阶段,无法直接复用已生成的聚合结果。

解决方法

方法1:子查询预计算每日总和(适用于连续日期)

先通过子查询得到按日期分组的每日销售额总和,再在外层查询中对该结果应用滚动平均窗口函数:

from django.db.models import Sum, Avg, F, Window
from django.db.models.expressions import RowRange

# 子查询:计算每日销售额总和
daily_sums = Sales.objects.values('date').annotate(sum_for_date=Sum('value')).order_by('date')

# 外层查询:计算7天滚动平均(含当天)
result = daily_sums.annotate(
    rolling_avg=Window(
        expression=Avg('sum_for_date'),
        frame=RowRange(start=-6, end=0),  # 从当前行往前数6行+当前行,共7行数据
        order_by=F('date').asc()
    )
)

注意:RowRange是按行数计算窗口范围,仅当日期连续无缺失时结果准确。

方法2:基于日期范围的窗口计算(适用于日期有缺失的场景)

如果数据存在日期断层,RowRange会因为行数不匹配导致滚动平均失真,此时应使用RangeFrame结合日期范围定义窗口:

PostgreSQL适配写法

from django.db.models import Sum, Avg, F, Window
from django.db.models.expressions import RangeFrame

result = Sales.objects.values('date').annotate(
    sum_for_date=Sum('value')
).annotate(
    rolling_avg=Window(
        expression=Avg('sum_for_date'),
        frame=RangeFrame(start=F('date') - 6, end=F('date')),  # 覆盖过去6天到当天的范围
        order_by=F('date').asc()
    )
).order_by('date')

MySQL适配写法

MySQL不支持直接的日期加减作为RangeFrame参数,需要自定义函数实现日期偏移:

from django.db.models import Sum, Avg, F, Window, Func
from django.db.models.expressions import RangeFrame

class DateSub6Days(Func):
    function = 'DATE_SUB'
    template = "%(function)s(%(expressions)s, INTERVAL 6 DAY)"

result = Sales.objects.values('date').annotate(
    sum_for_date=Sum('value')
).annotate(
    rolling_avg=Window(
        expression=Avg('sum_for_date'),
        frame=RangeFrame(start=DateSub6Days(F('date')), end=F('date')),
        order_by=F('date').asc()
    )
).order_by('date')

内容的提问来源于stack exchange,提问作者hemabe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 20:45:27