Django注解实现中位数计算 基于MySQL5.8的ORM分组查询方案
Django ORM + MySQL 5.8 实现分组统计价格中位数方案
MySQL 5.8(即后续正式命名的MySQL 8.0版本)已经内置了百分位计算函数,你可以通过Django自定义聚合函数的方式直接在ORM层实现中位数统计,无需额外处理。
方案1:ORM层直接计算(推荐,大数据量适用)
第一步:定义自定义中位数聚合类
from django.db.models import Aggregate, FloatField class Median(Aggregate): # PERCENTILE_CONT 为连续插值中位数,需要取实际存在的数值可替换为 PERCENTILE_DISC function = 'PERCENTILE_CONT' name = 'median' template = '%(function)s(0.5) WITHIN GROUP (ORDER BY %(expressions)s)' output_field = FloatField()
第二步:替换原查询中的median字段
price_query = Product.objects \ .filter(price_query) \ .annotate(dt=Trunc('StartDate', frequency)) \ .values('dt') \ .annotate( avg_price=Avg('Price'), std_price=StdDev('Price'), count=Count('Price'), max_price=Max('Price'), min_price=Min('Price'), median=Median('Price') # 仅需替换此处即可 ) \ .order_by('dt')
该方案直接在数据库层计算,性能和原生SQL一致,返回结果完全匹配你给出的JSON格式要求。
方案2:Python层计算(小数据量适用)
如果你的MySQL版本实际低于8.0不支持窗口函数,且数据量不大,可以先拉取分组后的所有价格,在Python层计算指标:
from collections import defaultdict from statistics import mean, stdev, median # 拉取所有符合条件的日期和对应价格 raw_items = Product.objects.filter(price_query)\ .annotate(dt=Trunc('StartDate', frequency))\ .values('dt', 'Price')\ .order_by('dt') # 按日期分组计算 grouped = defaultdict(list) for item in raw_items: grouped[item['dt']].append(item['Price']) result = [] for dt, prices in grouped.items(): prices.sort() result.append({ "date": dt, "avg_price": mean(prices), "std_price": stdev(prices) if len(prices)>=2 else 0, "min_price": min(prices), "max_price": max(prices), "count": len(prices), "median": median(prices) })
注意事项
- 若执行方案1时提示函数不存在,可确认你的MySQL版本是否为8.0系列(MySQL 8.0开发阶段曾短暂使用5.8作为版本号命名),低于该版本无法直接使用内置百分位函数。
- PERCENTILE_CONT返回的是插值后的连续值,PERCENTILE_DISC返回的是数据集中实际存在的数值,可根据业务需求选择替换。
内容的提问来源于stack exchange,提问作者party911
相关产品推荐
相关产品推荐

