You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 20:03:22