如何用Django单查询集按月份统计两个模型的对象数量
解决方案
推荐方案:使用TruncMonth分组并补全缺失月份
这个方案能完美匹配你的目标格式,包含所有连续月份(即使某类车型无记录也显示0):
1. 导入所需依赖
from django.db.models import Count, F from django.db.models.functions import TruncMonth from django.utils.dateformat import DateFormat from datetime import datetime
2. 分别统计两类车型的月度数据
单独统计避免跨模型关联导致的重复计数问题:
# 统计Car的月度数量 car_monthly = Car.objects.annotate( month=TruncMonth('car_purchase_date') ).values('month').annotate( car_count=Count('id') ).order_by('month') # 统计Truck的月度数量 truck_monthly = Truck.objects.annotate( month=TruncMonth('truck_purchase_date') ).values('month').annotate( truck_count=Count('id') ).order_by('month')
3. 生成时间范围内的所有连续月份
确保不会遗漏任何月份:
# 获取数据覆盖的时间范围(自动取最早和最晚记录) min_car_date = Car.objects.aggregate(min=F('car_purchase_date'))['min'] min_truck_date = Truck.objects.aggregate(min=F('truck_purchase_date'))['min'] min_date = min(min_car_date, min_truck_date) if min_car_date and min_truck_date else datetime.now() max_date = datetime.now() # 生成所有连续月份 current_month = min_date.replace(day=1) all_months = [] while current_month <= max_date: all_months.append(current_month) # 切换到下一个月 if current_month.month == 12: current_month = current_month.replace(year=current_month.year + 1, month=1) else: current_month = current_month.replace(month=current_month.month + 1)
4. 合并统计结果并格式化
把两类车型的数据匹配到对应的月份,生成最终统计列表:
# 转成字典快速查找 car_data = {item['month'].replace(day=1): item['car_count'] for item in car_monthly} truck_data = {item['month'].replace(day=1): item['truck_count'] for item in truck_monthly} # 生成最终统计数据 final_stats = [] for month in all_months: formatted_month = DateFormat(month).format('F Y') # 格式化为"January 2023" final_stats.append({ 'month': formatted_month, 'car_count': car_data.get(month, 0), 'truck_count': truck_data.get(month, 0) })
5. 输出目标表格格式
如果需要打印成指定的表格样式:
# 打印表头 print("+---------------+-----------+-------------+") print("| Month | Car Count | Truck Count |") print("+---------------+-----------+-------------+") # 打印每行数据 for stat in final_stats: print(f"| {stat['month']:<13} | {stat['car_count']:>9} | {stat['truck_count']:>11} |") print("+---------------+-----------+-------------+")
简化方案:直接分组统计(不补全缺失月份)
如果不需要显示无记录的月份,可以直接按年月分组统计,代码更简洁:
from django.db.models import Count, IntegerField from django.db.models.functions import ExtractYear, ExtractMonth monthly_stats = ( User.objects.annotate( year=ExtractYear('car__car_purchase_date', output_field=IntegerField()), month=ExtractMonth('car__car_purchase_date', output_field=IntegerField()) ) .union( User.objects.annotate( year=ExtractYear('truck__truck_purchase_date', output_field=IntegerField()), month=ExtractMonth('truck__truck_purchase_date', output_field=IntegerField()) ) ) .values('year', 'month') .annotate( car_count=Count('car__id', distinct=True), truck_count=Count('truck__id', distinct=True) ) .order_by('year', 'month') )
注意:该方案不会自动补全没有任何记录的月份,需要额外处理才能匹配目标格式。
内容的提问来源于stack exchange,提问作者blueblob26
相关产品推荐
相关产品推荐

