Django日期字段聚合问题:按门店每日统计销售额分组失效
问题分析与解决方案
核心问题
你的代码存在三个关键错误:
- 未关联当前门店:
SalesSerializer中使用self.queryset调用的是全量销售数据,没有过滤当前门店的记录,导致所有门店返回相同的统计结果。 - 日期分组错误:
ExtractWeekDay提取的是星期几(1-7),不是按自然日(年月日)统计,不符合“每日统计”的需求。 - 关联字段配置错误:
Sales模型的ForeignKey参数写错(应该是related_name而非related_field),且StoreSerializer中many=False不符合一对多的关联逻辑。
步骤1:修正模型错误
首先修复Sales模型的参数错误,补全字段类型:
class Store(models.Model): store_name = models.CharField(max_length=100) class Meta: db_table = 'stores' class Sales(models.Model): # 修正related_field为related_name,补全BigIntegerField store = models.ForeignKey(Store, on_delete=models.CASCADE, related_name='sales') product_id = models.BigIntegerField() sale_date = models.DateTimeField() total_mrp = models.FloatField()
步骤2:正确实现按门店每日统计
推荐两种实现方式,按需选择:
方式一:SerializerMethodField 直接处理(简单直观)
在StoreSerializer中通过SerializerMethodField对当前门店的销售数据按日期分组求和:
from django.db.models import Sum from django.db.models.functions import ExtractDate from rest_framework import serializers class StoreSerializer(serializers.ModelSerializer): # 自定义字段返回每日统计数据 daily_sales = serializers.SerializerMethodField() class Meta: model = Store exclude = ['id'] def get_daily_sales(self, obj): # 对当前门店的销售记录按日期分组,计算total_mrp总和 stats = obj.sales.values( # 提取日期部分(忽略时分秒) sale_date=ExtractDate('sale_date') ).annotate( total_sale=Sum('total_mrp') ).order_by('sale_date') # 转换为列表返回(QuerySet转字典列表) return list(stats)
方式二:预加载分组数据(性能更优)
如果数据量较大,用Prefetch提前预加载分组后的销售数据,减少数据库查询次数:
from django.db.models import Sum, Prefetch, Q, F from django.db.models.functions import ExtractDate from rest_framework import serializers, viewsets # 定义每日统计的序列化器 class DailySalesStatsSerializer(serializers.Serializer): sale_date = serializers.DateField() total_sale = serializers.FloatField() class StoreSerializer(serializers.ModelSerializer): daily_sales = DailySalesStatsSerializer(many=True, read_only=True) class Meta: model = Store exclude = ['id'] class StoreViewSet(viewsets.ReadOnlyModelViewSet): queryset = Store.objects.prefetch_related( Prefetch( 'sales', # 提前对销售数据按门店+日期分组求和 queryset=Sales.objects.values( 'store', sale_date=ExtractDate('sale_date') ).annotate(total_sale=Sum('total_mrp')).order_by('sale_date'), to_attr='daily_sales' ) ) serializer_class = StoreSerializer
关键说明
- 使用
ExtractDate替代ExtractWeekDay:确保按具体的年月日分组,而非星期几。 - 基于当前门店对象查询:通过
obj.sales获取当前门店的关联销售记录,避免全量数据查询。 - 一对多关联配置:
related_name='sales'让Store对象可以通过obj.sales直接访问关联的销售记录,many=True适配一对多的关联关系。
内容的提问来源于stack exchange,提问作者Haris Shah
相关产品推荐
相关产品推荐

