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

如何使用Django ORM按用户分组查询指定日期范围的amount累计求和值

问题描述

我有一张财务数据表,字段包含user(用户标识)、amount(金额)、date(记录日期),示例数据如下:

useramountdate
John102017-07-01
John202019-07-01
John302020-09-01
John402021-11-01
.........
Dan-202019-02-01
Dan-302020-04-01
Dan-402021-06-01

函数入参为日期区间,示例如下:

date_start = '2019-01-01'
date_end = '2021-11-07'

预期输出

针对所有用户,返回指定日期区间内每个日期对应的、从最早记录到当前日期的amount累计求和值,效果如下:

useramountdate
John30 (10+20)2019-07-01
John60 (10+20+30)2020-09-01
John100 (10+20+30+40)2021-11-01
.........
Dan-202019-02-01
Dan-50 (-20-30)2020-04-01
Dan-90 (-20-30-40)2021-06-01

现有问题

我编写了如下代码实现该功能:

def get_sum_amount(self, date_start=None, date_end=None):
    date_detail = {}
    # 最小日期过滤
    if date_start:
        date_detail['date__gt'] = date_start

    # 最大日期过滤
    if date_end:
        date_detail['date__lt'] = date_end

    detail = Financial.objects.filter(Q(**date_detail)) \
        .values('user') \
        .annotate(sum_amount=Coalesce(Sum(F('amount')), Value(0)))

运行后仅返回了指定日期范围内每个用户的amount总求和值,不符合预期:

useramount
John100
Dan-90

解决方案

原有代码存在两个核心问题:

  1. 一开始就过滤掉了早于date_start的记录,导致这部分金额没有被计入累计值
  2. 只按user分组聚合,没有保留日期维度,且用普通的Sum只能得到分组总求和,无法实现按日期的滚动累计

修改后的代码如下:

from django.db.models import Sum, F, Window, Q, Coalesce, Value

def get_sum_amount(self, date_start=None, date_end=None):
    # 第一步:先过滤出所有不晚于date_end的记录,早于date_start的要保留参与累计计算
    filter_params = Q()
    if date_end:
        filter_params &= Q(date__lte=date_end)
    
    # 按用户+日期分组聚合,处理同一用户同一天有多条记录的场景
    queryset = Financial.objects.filter(filter_params) \
        .values('user', 'date') \
        .annotate(daily_amount=Coalesce(Sum('amount'), Value(0))) \
        .order_by('user', 'date')
    
    # 第二步:用窗口函数计算每个用户的累计金额
    queryset = queryset.annotate(
        cumulative_amount=Window(
            expression=Sum('daily_amount'),
            partition_by=[F('user')],  # 按用户分组计算累计
            order_by=[F('date').asc()] # 按日期升序滚动求和
        )
    )
    
    # 第三步:过滤出日期在指定区间内的结果返回
    if date_start:
        queryset = queryset.filter(date__gte=date_start)
    
    # 调整字段名匹配预期输出
    return queryset.values('user', amount=F('cumulative_amount'), 'date')

逻辑说明

  • 窗口函数Window的默认计算范围就是「当前分组内从第一条记录到当前行」,正好符合从最早记录到当前日期累计求和的需求
  • 先保留所有早于等于date_end的记录计算累计,最后再过滤出区间内的记录,不会丢失区间外的金额累计
  • 提前按用户+日期做每日金额聚合,避免同一天多条数据导致的计算错误

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 07:06:01