如何用Django ORM高效计算各车型占比(单查询实现)
Django ORM 单查询实现车型分组占比计算
问题原因
你的代码中,第一个annotate(total=Count('pk'))在执行values('car_type_name')后,total会被重新计算为当前分组内的记录数,而非全局总车辆数。这就导致每组的car_type_count和total数值相等,最终占比全部为100%。
解决方案
以下两种方案均为单查询实现,避免迭代式计算带来的性能损耗:
方案1:使用Subquery获取全局总数量
from django.db.models import Subquery, OuterRef, Count, FloatField, F, Cast def calculate_car_type_ratio(cars_queryset): # 子查询获取当前QuerySet的总车辆数 total_subquery = cars_queryset.values('pk').annotate(total=Count('pk')).values('total') result = cars_queryset.values('car_type__name')\ .annotate( this_car_type_count=Count('id'), total=Subquery(total_subquery[:1]) )\ .annotate( percentage=Cast(F('this_car_type_count') * 100.0 / F('total'), FloatField()) )\ .values( car_type=F('car_type__name'), percentage=F('percentage'), this_car_type_count=F('this_car_type_count') ) return list(result)
- 原理:通过子查询提前计算输入QuerySet的全局总车辆数,分组时直接引用该值,避免分组后重新计算
total。 - 兼容性:支持所有Django版本,适合老项目使用。
方案2:使用Window函数(Django 2.0+)
from django.db.models import Window, Count, FloatField, F, Cast def calculate_car_type_ratio(cars_queryset): result = cars_queryset.annotate( # 使用Window函数计算全局总车辆数 total=Window(expression=Count('id')) )\ .values('car_type__name')\ .annotate( this_car_type_count=Count('id'), percentage=Cast(F('this_car_type_count') * 100.0 / F('total'), FloatField()) )\ .values( car_type=F('car_type__name'), percentage=F('percentage'), this_car_type_count=F('this_car_type_count') )\ .distinct() # 去重,消除Window函数带来的重复分组记录 return list(result)
- 原理:Window函数允许在不分组的前提下计算全局聚合值,后续分组时直接使用该值计算占比。
- 优势:代码更简洁,可读性更强,适合使用Django 2.0及以上版本的项目。
输出示例
调用上述函数后,会返回符合要求的字典列表:
[ {'car_type': 'car01', 'percentage': 70.0, 'this_car_type_count': 20}, {'car_type': 'car02', 'percentage': 30.0, 'this_car_type_count': 9}, ... ]
内容的提问来源于stack exchange,提问作者Muhammad Hammad
相关产品推荐
相关产品推荐

