如何在Django ORM中正确聚合多对多关联的费用数据?
Django ORM按团队和日期分组计算报销总额(解决重复统计问题)
问题原因
由于ExpenseReport与MyPerson是多对多关联,同一个报销单若被多个同团队人员关联,直接使用values().annotate(Sum(...))会导致该报销单金额被重复计算,最终结果出现翻倍或多倍误差。
解决方案
核心思路是先确保同一个报销单针对同一个团队仅被统计一次,再进行分组求和。以下是两种可行的ORM实现方式:
方法一:先去重再分组求和
from django.db.models import F, Sum # 1. 生成去重后的团队-报销单-日期-金额记录 # 通过distinct确保同一团队、同一报销单、同一日期的记录仅出现一次 unique_records = MyTeam.objects.filter( myperson__expensereport__isnull=False ).annotate( expense_date=F('myperson__expensereport__expense_date'), expense_paid=F('myperson__expensereport__expense_paid'), report_id=F('myperson__expensereport__id') ).distinct('id', 'report_id', 'expense_date') # 2. 按团队名称和日期分组,计算总报销额 final_result = unique_records.values('name', 'expense_date').annotate( total_expense=Sum('expense_paid') ).order_by('name', 'expense_date')
方法二:使用子查询实现精准求和
from django.db.models import Sum, Subquery, OuterRef, Coalesce # 子查询:针对指定团队和日期,获取所有唯一报销单的金额总和 sum_subquery = Subquery( ExpenseReport.objects.filter( persons__team=OuterRef('pk'), expense_date=OuterRef('myperson__expensereport__expense_date') ).distinct().values_list(Sum('expense_paid'), flat=True) ) # 主查询:按团队和日期分组,聚合结果(用Coalesce处理无报销的情况,返回0) final_result = MyTeam.objects.values('name', 'myperson__expensereport__expense_date').annotate( total_expense=Coalesce(Sum(sum_subquery), 0.0) ).order_by('name', 'myperson__expensereport__expense_date')
结果说明
两种方法均能避免重复统计,最终final_result返回结构示例:
<QuerySet [ {'name': '团队A', 'expense_date': datetime.date(2024, 1, 1), 'total_expense': 1500.0}, {'name': '团队B', 'expense_date': datetime.date(2024, 1, 1), 'total_expense': 800.0}, ... ]>
内容的提问来源于stack exchange,提问作者Ziv
相关产品推荐
相关产品推荐

