Django annotate统计主模型字段SUM值异常问题求助
问题根源
当你同时关联Tonic和Cross两个外键模型做聚合统计时,Django ORM会生成带笛卡尔积的SQL查询——主模型的单条记录会被重复关联多次(次数等于对应Tonic记录数 × Cross记录数),导致主模型字段的SUM被重复计算,最终数值被放大几十甚至上百倍。
解决方案:用子查询分离主模型与关联模型的统计
通过Subquery和OuterRef分别对主模型字段、关联模型的revenue做独立统计,彻底避免笛卡尔积影响,同时不会丢失数据。
具体实现步骤
假设你的模型结构大致如下:
class Statistic(models.Model): campaign_name = models.CharField(max_length=255) spend = models.DecimalField(max_digits=10, decimal_places=2) impressions = models.IntegerField() class Tonic(models.Model): statistic = models.ForeignKey(Statistic, on_delete=models.CASCADE, related_name='tonics') revenue = models.DecimalField(max_digits=10, decimal_places=2) class Cross(models.Model): statistic = models.ForeignKey(Statistic, on_delete=models.CASCADE, related_name='crosses') revenue = models.DecimalField(max_digits=10, decimal_places=2)
在你的StatisticViewSet中,修改get_queryset方法如下:
from django.db.models import Sum, Subquery, OuterRef class StatisticViewSet(viewsets.ModelViewSet): serializer_class = StatisticSerializer def get_queryset(self): # 子查询统计主模型每个campaign_name的spend、impressions总和 main_stats_subquery = Statistic.objects.filter( campaign_name=OuterRef('campaign_name') ).values('campaign_name').annotate( total_spend=Sum('spend'), total_impressions=Sum('impressions') ).values('total_spend', 'total_impressions') # 子查询统计每个campaign_name对应的Tonic revenue总和 tonic_revenue_subquery = Tonic.objects.filter( statistic__campaign_name=OuterRef('campaign_name') ).values('statistic__campaign_name').annotate( total_tonic_revenue=Sum('revenue') ).values('total_tonic_revenue') # 子查询统计每个campaign_name对应的Cross revenue总和 cross_revenue_subquery = Cross.objects.filter( statistic__campaign_name=OuterRef('campaign_name') ).values('statistic__campaign_name').annotate( total_cross_revenue=Sum('revenue') ).values('total_cross_revenue') # 主查询分组并关联子查询结果 return Statistic.objects.values('campaign_name').annotate( total_spend=Subquery(main_stats_subquery.values('total_spend')[:1]), total_impressions=Subquery(main_stats_subquery.values('total_impressions')[:1]), total_tonic_revenue=Subquery(tonic_revenue_subquery.values('total_tonic_revenue')[:1], output_field=models.DecimalField()), total_cross_revenue=Subquery(cross_revenue_subquery.values('total_cross_revenue')[:1], output_field=models.DecimalField()) ).order_by('campaign_name')
关键说明
- 主模型的统计通过单独子查询完成,完全不关联Tonic和Cross,避免了笛卡尔积导致的重复计算。
- 关联模型的revenue分别用独立子查询统计,互相不干扰,保证数据准确。
- 每个
Subquery末尾加[:1]确保只返回单个聚合值,符合子查询的返回要求。
内容的提问来源于stack exchange,提问作者Andrii
相关产品推荐
相关产品推荐

