如何使用Django ORM对查询集进行价格分箱统计?
使用Django ORM按价格区间统计产品数据
嘿,针对你提出的按价格区间统计产品总数、总销售额、平均销售额的需求,我结合你给出的Django模型,整理了两种可行的实现方案,对应你提到的两个思路:
先看你的模型结构
from django.db import models class Category(models.Model): name = models.CharField(max_length=264) def __str__(self): return self.name class Meta: verbose_name_plural = "categories" class Status(models.Model): name = models.CharField(max_length=264) def __str__(self): return self.name class Meta: verbose_name_plural = "status" class Product(models.Model): title = models.CharField(max_length=264) description = models.CharField(max_length=264) category = models.ForeignKey(Category, on_delete=models.CASCADE) price = models.DecimalField(max_digits=10, decimal_places=2) # 补充decimal_places,原模型遗漏该参数 status = models.ForeignKey(Status, on_delete=models.CASCADE)
方案一:查询集转列表后用Python函数处理
这种方法适合数据量不大的场景,逻辑直观,容易调整区间规则:
- 先获取目标产品数据集(可按需添加过滤条件,比如特定分类、在售状态)
- 定义价格区间规则,遍历产品列表完成归类统计
示例代码:
from .models import Product def stats_by_price_range(): # 定义价格区间:键为区间名称,值为(下限, 上限),明确边界逻辑 price_ranges = { "0-100": (0, 100), "100-500": (100, 500), "500-1000": (500, 1000), "1000+": (1000, float('inf')) } # 获取产品数据,可添加filter筛选目标产品 products = Product.objects.all() # 初始化统计结果容器 stats = {range_name: {"total_count": 0, "total_sales": 0, "avg_price": 0} for range_name in price_ranges} for product in products: for range_name, (low, high) in price_ranges.items(): if low <= product.price < high: stats[range_name]["total_count"] += 1 stats[range_name]["total_sales"] += product.price break # 找到对应区间后跳出循环,避免重复判断 # 计算各区间平均价格 for range_name in stats: count = stats[range_name]["total_count"] if count > 0: stats[range_name]["avg_price"] = stats[range_name]["total_sales"] / count return stats
调用该函数即可得到每个区间的完整统计数据。
方案二:直接用Django ORM在数据库层面分箱统计
这种方法效率更高,适合大数据量场景,所有统计逻辑由数据库完成,无需将全量数据加载到Python内存:
核心思路是用Case和When给每个产品标记所属价格区间,再按区间分组完成统计计算。
示例代码:
from django.db.models import Case, When, Value, Count, Sum, Avg from .models import Product def stats_by_price_range_orm(): # 定义价格区间判断规则,明确边界避免重复统计(示例中0-100包含100,100-500包含500) price_range_case = Case( When(price__lte=100, then=Value("0-100")), When(price__gt=100, price__lte=500, then=Value("100-500")), When(price__gt=500, price__lte=1000, then=Value("500-1000")), When(price__gt=1000, then=Value("1000+")), output_field=models.CharField(), ) # 执行分组统计 stats_queryset = Product.objects.annotate( price_range=price_range_case ).values( 'price_range' ).annotate( total_count=Count('id'), total_sales=Sum('price'), avg_price=Avg('price') ).order_by('price_range') # 转换为字典格式,方便业务使用 return {item['price_range']: { 'total_count': item['total_count'], 'total_sales': item['total_sales'], 'avg_price': item['avg_price'] } for item in stats_queryset}
该方案直接生成优化后的SQL查询,数据库高效完成分组计算,返回结果已包含所有统计指标。
注意事项
- 统一价格区间的边界逻辑,比如明确100元属于哪个区间,避免统计重复或遗漏
- 若你的
price字段未设置decimal_places,务必补充该参数,否则会影响数值存储与计算 - 可根据业务需求在查询中添加
filter条件,比如只统计特定分类、状态的产品
内容的提问来源于stack exchange,提问作者IordanouGiannis
相关产品推荐
相关产品推荐

