Django - 跨查询集的 DatetimeField 时间聚合

Django - Time aggregates of DatetimeField across queryset

(使用 django 1.11.2,python 2.7.10,mysql 5.7.18)

如果我们想象一个简单的模型:

class Event(models.Model):
    happened_datetime = DateTimeField()
    value = IntegerField()

运行 类似于以下内容的最优雅(也是最快)的方法是什么:

res = Event.objects.all().aggregate(
    Avg('happened_datetime')
)

但这将能够提取查询集所有成员的平均时间。类似于:

res = Event.objects.all().aggregate(
    AvgTimeOfDay('happened_datetime')
)

是否可以直接在数据库上执行此操作?即,无需 运行为每个查询集成员设置长循环客户端?

编辑:

按照这些思路,使用原始 SQL:

可能有一个解决方案
select sec_to_time(avg(time_to_sec(extract(HOUR_SECOND from happened_datetime)))) from event_event;

性能方面,这 运行s 在 0.015 秒内用于笔记本电脑上的 ~23k 行,未优化等。假设 可以 产生 accurate/correct 结果,因为时间只是次要因素,我可以使用它吗?

向您的模型添加另一个整数字段,该字段仅包含从 happened_datetime.

中提取的一天中的小时

当 creating/updating 模型实例时,只要 happened_datetime 为 set/updated,您就需要相应地更新此新字段。例如,您可以通过阅读 datetime.datetime.hour 来提取一天中的小时数。或者使用 strftime 创建一个你喜欢的值。

然后聚合应该按照您的建议工作。

编辑:

Django 的 ORM 有一个函数 Extract()。适用于您的用例的文档中的示例:

>>> # How many experiments completed in the same year in which they started?
>>> Event.objects.aggregate(
...    happenend_datetime__hour=Extract('happenend_datetime', 'hour'))

(未测试!) https://docs.djangoproject.com/en/1.11/ref/models/database-functions/#extract

所以经过一些搜索和尝试.. 下面似乎有效。欢迎任何关于如何改进的评论(或暗示为什么它是完全错误的)! :-)

res = Event.objects.raw('''
SELECT id, sec_to_time(avg(time_to_sec(extract(HOUR_SECOND from happened_datetime)))) AS average_time_of_day
FROM event_event
WHERE happened_datetime BETWEEN %s AND %s;''', [start_datetime, end_datetime])

print res[0].__dict__
# {'average_time_of_day': datetime.time(18, 48, 10, 247700), '_state': <django.db.models.base.ModelState object at 0x0445B370>, 'id': 9397L}

现在返回的 ID 是落在 WHERE 子句的日期时间范围内的最后一个对象的 ID。我相信 Django 只是插入它是因为 "InvalidQuery: Raw query must include the primary key".

SQL 系列函数调用的快速解释:

  1. 从所有日期时间字段中提取 HH:MM:SS
  2. 通过 time_to_sec.
  3. time 值转换为秒
  4. 平均所有秒值
  5. 将平均秒值转换回时间格式 (HH:MM:SS)

不知道为什么 Django 坚持返回微秒,但这并不真正相关。 (也许是实例化时间对象的本地毫秒?)

性能说明:这似乎非常快,但我又没有测试过那一点。任何见解将不胜感激:)