我希望将每天的所有活动持续时间相加。这是我的模特:
class Event(models.Model):
start = models.DateTimeField()
end = models.DateTimeField()
示例数据:
import datetime
from random import randint
for i in range(0, 1000):
start = datetime.datetime(
year=2016,
month=1,
day=randint(1, 10),
hour=randint(0, 23),
minute=randint(0, 59),
second=randint(0, 59)
)
end = start + datetime.timedelta(seconds=randint(30, 1000))
Event.objects.create(start=start, end=end)
我可以像每天一样获得事件计数:
(我知道extra
很糟糕,但我目前正在使用1.9。当我升级时,我会转而使用TruncDate
)
Event.objects.extra({'date': 'date(start)'}).order_by('date').values('date').annotate(count=Count('id'))
[{'count': 131, 'date': datetime.date(2016, 1, 1)},
{'count': 95, 'date': datetime.date(2016, 1, 2)},
{'count': 99, 'date': datetime.date(2016, 1, 3)},
{'count': 85, 'date': datetime.date(2016, 1, 4)},
{'count': 87, 'date': datetime.date(2016, 1, 5)},
{'count': 94, 'date': datetime.date(2016, 1, 6)},
{'count': 97, 'date': datetime.date(2016, 1, 7)},
{'count': 111, 'date': datetime.date(2016, 1, 8)},
{'count': 97, 'date': datetime.date(2016, 1, 9)},
{'count': 104, 'date': datetime.date(2016, 1, 10)}]
我可以注释添加持续时间:
In [3]: Event.objects.annotate(duration=F('end') - F('start')).first().duration
Out[3]: datetime.timedelta(0, 470)
但我无法弄清楚如何对这个注释求和,就像我可以计算事件一样。我已经尝试过以下操作,但我的KeyError
时间为{#1}}。
Event.objects.annotate(duration=F('end') - F('start')).extra({'date': 'date(start)'}).order_by('date').values('date').annotate(total_duration=Sum('duration'))
如果我将duration
添加到values
子句,则它不再按日期分组。
这是否可以在单个查询中进行并且不向模型添加持续时间字段?
答案 0 :(得分:2)
我正准备写一个Django ORM不支持这个问题的答案。是的,然后我花了一个小时来解决这个问题(除了在开始写这个答案之前花了1.5小时),但事实证明,Django确实支持它。而且没有黑客攻击。好消息!
import datetime as dt
from django.db import models
from django.db.models import F, Sum, When, Case
from django.db.models.functions import TruncDate
from app.models import Event
a = Event.objects.annotate(date=TruncDate('start')).values('date').annotate(
day_duration=Sum(Case(
When(date=TruncDate(F('start')), then=F('end') - F('start')),
default=dt.timedelta(), output_field=models.DurationField()
))
)
一些初步测试(希望)证明这些东西确实能满足你的要求。
In [71]: a = Event.objects.annotate(date=TruncDate('start')).values('date').annotate(day_duration=Sum(Case(
...: When(date=TruncDate(F('start')), then=F('end') - F('start')),
...: default=dt.timedelta(), output_field=models.DurationField()
...: ))
...: )
In [72]: for e in a:
...: print(e)
...:
{'day_duration': datetime.timedelta(0, 41681), 'date': datetime.date(2016, 1, 10)}
{'day_duration': datetime.timedelta(0, 46881), 'date': datetime.date(2016, 1, 3)}
{'day_duration': datetime.timedelta(0, 48650), 'date': datetime.date(2016, 1, 1)}
{'day_duration': datetime.timedelta(0, 52689), 'date': datetime.date(2016, 1, 8)}
{'day_duration': datetime.timedelta(0, 45788), 'date': datetime.date(2016, 1, 5)}
{'day_duration': datetime.timedelta(0, 49418), 'date': datetime.date(2016, 1, 7)}
{'day_duration': datetime.timedelta(0, 45984), 'date': datetime.date(2016, 1, 9)}
{'day_duration': datetime.timedelta(0, 51841), 'date': datetime.date(2016, 1, 2)}
{'day_duration': datetime.timedelta(0, 63770), 'date': datetime.date(2016, 1, 4)}
{'day_duration': datetime.timedelta(0, 57205), 'date': datetime.date(2016, 1, 6)}
In [73]: q = dt.timedelta()
In [74]: o = Event.objects.filter(start__date=dt.date(2016, 1, 7))
In [75]: p = Event.objects.filter(start__date=dt.date(2016, 1, 10))
In [76]: for e in o:
...: q += (e.end - e.start)
In [77]: q
Out[77]: datetime.timedelta(0, 49418) # Matches 2016.1.7, yay!
In [78]: q = dt.timedelta()
In [79]: for e in p:
...: q += (e.end - e.start)
In [80]: q
Out[80]: datetime.timedelta(0, 41681) # Matches 2016.1.10, yay!
NB!这适用于1.9版本,我不认为您可以使用早期版本执行此操作,因为缺少TruncDate
函数。在1.8之前,你当然也没有Case
和When
件事。