Django ORM如何按外键分组计算两日期时间差的平均值
模型定义
class Makes(models.Model): student = models.ForeignKey(...) instrument = models.ForeignKey( to='users.Instrument', on_delete=models.CASCADE ) begin_date = models.DateField( null=True, blank=True ) end_date = models.DateField( null=True, blank=True )
需求
针对每个instrument外键关联对象,统计对应Makes记录中begin_date和end_date之间timedelta的平均值,最终返回每个乐器名称对应平均时长的QuerySet,格式如下:
<QuerySet[ { 'instrument__instrument_name': 'alt-viool', 'duration': datetime.timedelta(days=142) }, { 'instrument__instrument_name': 'contrabas', 'duration': datetime.timedelta(days=62) }, { 'instrument__instrument_name': 'viool', 'duration': datetime.timedelta(days=49) } ]>
原有写法问题
- 第一种写法用了
aggregate(),该方法是对全表做全局聚合,不会按维度分组,自然无法拿到每个乐器的单独统计值和乐器名称。 - 第二种写法把
annotate()放在了values()前面,Django ORM会默认按模型主键分组,给每一条Makes记录加注解,不会按乐器维度聚合。
正确ORM写法
from django.db.models import Avg, F, ExpressionWrapper, fields # 定义单条记录的时长计算表达式 duration_expr = ExpressionWrapper( F('end_date') - F('begin_date'), output_field=fields.DurationField() ) instrument_data = ( Makes.objects # 过滤掉日期为空的无效记录 .exclude(begin_date__isnull=True) .exclude(end_date__isnull=True) # 先指定分组维度:按乐器名称分组 .values('instrument__instrument_name') # 分组后聚合计算每个组的平均时长 .annotate(duration=Avg(duration_expr)) )
关键规则
Django ORM分组聚合的执行逻辑和调用顺序强相关:
- 先调用
values()指定分组字段,再调用annotate()做聚合计算,会按values()中指定的字段分组,返回每个分组的聚合结果。 - 先调用
annotate()再调用values(),会给每一行原始记录添加注解字段,不会触发分组聚合。 - 全局聚合用
aggregate(),分组聚合用annotate()。
内容的提问来源于stack exchange,提问作者developing
相关产品推荐
相关产品推荐

