聚合分组注释
Aggregate grouped annotation
我想对每天的所有事件持续时间求和。这是我的模型:
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)
但我不知道如何用计算事件的方式来计算这个注释的总和。我尝试了以下方法,但在 'duration'.
上得到了 KeyError
Event.objects.annotate(duration=F('end') - F('start')).extra({'date': 'date(start)'}).order_by('date').values('date').annotate(total_duration=Sum('duration'))
并且如果我将 duration
添加到 values
子句,则它不再按日期分组。
在不向模型添加持续时间字段的情况下,这是否可以在单个查询中实现?
我正要写一个答案说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!
注意!这适用于 1.9 版,我认为您不能使用早期版本执行此操作,因为缺少 TruncDate
函数。在 1.8 之前,您当然也没有 Case
和 When
东西。
我想对每天的所有事件持续时间求和。这是我的模型:
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)
但我不知道如何用计算事件的方式来计算这个注释的总和。我尝试了以下方法,但在 'duration'.
上得到了KeyError
Event.objects.annotate(duration=F('end') - F('start')).extra({'date': 'date(start)'}).order_by('date').values('date').annotate(total_duration=Sum('duration'))
并且如果我将 duration
添加到 values
子句,则它不再按日期分组。
在不向模型添加持续时间字段的情况下,这是否可以在单个查询中实现?
我正要写一个答案说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!
注意!这适用于 1.9 版,我认为您不能使用早期版本执行此操作,因为缺少 TruncDate
函数。在 1.8 之前,您当然也没有 Case
和 When
东西。