1

(使用 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;

性能方面,这在笔记本电脑上运行约 23k 行的时间为 0.015 秒,未进行优化等。假设这可以产生准确/正确的结果,并且由于时间只是次要因素,我可以使用它吗?

4

2 回答 2

2

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

创建/更新模型实例时,您需要在happened_datetime设置/更新时相应地更新此新字段。例如,您可以通过阅读来提取一天中的时间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

于 2017-09-12T13:05:02.350 回答
0

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

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:原始查询必须包含主键”。

SQL系列函数调用速释:

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

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

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

于 2017-09-12T17:44:43.907 回答