如何基于Django模型按类别统计账单金额总和?
问题描述
我有一个存储账单数据的表,每条账单都被归类,例如信用卡、贷款、个人账单等。我正在尝试统计每个类别的总金额。
我的Django模型定义如下:
class Debt(models.Model): Categories = [ ('Credit Card', 'Credit Card'), ('Mortgage', 'Mortgage'), ('Loan', 'Loan'), ('Household Bill', 'Household Bill'), ('Social', 'Social'), ('Personal', 'Personal') ] user = models.ForeignKey(User, on_delete=models.CASCADE) creditor = models.ForeignKey(Creditor, on_delete=models.CASCADE) amount = models.FloatField(blank=True, null=True) category = models.CharField(max_length=50, blank=True, null=True, choices=Categories)
我认为使用annotate是正确的方法,但不知道如何按类别分组来统计总额。目前的代码只能统计单个类别:
all_outgoing = Debt.objects.annotate(total=Sum('amount')).filter(category='personal')
我不想为每个类别单独写annotate,也不确定循环遍历类别做过滤查询是不是最优方案,想知道更好的实现方式。
解决方案
不需要循环每个类别单独查询,Django ORM可以通过values()与annotate()结合,一次性完成按类别分组的金额统计,这种方式仅需一次数据库请求,比循环查询效率高很多。
基础实现
通过values('category')指定分组字段,再用annotate()计算每个分组的金额总和:
from django.db.models import Sum category_totals = Debt.objects.values('category').annotate(total=Sum('amount'))
返回的QuerySet中每个元素是字典,格式示例:{'category': 'Credit Card', 'total': 1500.0},包含所有类别及其对应的总金额。
优化细节
- 过滤无效数据:如果存在
category为空或amount为空的记录,可提前过滤避免统计无效值:
category_totals = Debt.objects.exclude(category__isnull=True)\ .exclude(amount__isnull=True)\ .values('category')\ .annotate(total=Sum('amount'))
- 按用户筛选:若需统计特定用户的账单,添加用户过滤条件即可:
# 假设request.user为当前登录用户 category_totals = Debt.objects.filter(user=request.user)\ .exclude(category__isnull=True)\ .exclude(amount__isnull=True)\ .values('category')\ .annotate(total=Sum('amount'))
- 结果排序:可按总金额或类别排序,比如按总金额降序排列(空值排在最后):
from django.db.models import Sum, F category_totals = Debt.objects.values('category')\ .annotate(total=Sum('amount'))\ .order_by(F('total').desc(nulls_last=True))
为什么不推荐循环查询
循环遍历每个类别单独查询会触发多次数据库请求,随着数据量增长,性能损耗会越来越明显。而分组统计只需要一次数据库查询,在性能上远优于循环方案。
内容的提问来源于stack exchange,提问作者JacksWastedLife
相关产品推荐
相关产品推荐

