Django - 使用与该事件的开始日期时间重叠的参与者总和来注释每个事件
Django - Annotating each Event with Sum of participants that overlap with the start DateTime of that Event
我有一个带有开始日期时间和结束日期时间以及参与者人数的事件模型。
对于每个事件对象,我想获得与开始日期时间重叠的任何事件中所有参与者的注释总和。这样我就可以确保在任何给定时间没有太多参与者。
class Event(models.Model):
start = models.DateTime()
end = models.DateTime()
participants = models.IntegerField()
我一直在阅读有关 Window 函数的信息,也许它可以在这里工作,但我无法正确理解。
我试过了,但它不起作用,因为它希望将具有相同开始日期时间的事件组合在一起,而不是将开始和结束日期时间与原始事件开始日期时间重叠。
starts = Event.objects.annotate(
participants_sum=Window(
expression=Sum('participants'),
partition_by=[F('start'),],
order_by=ExtractDay('start').asc(),
),
).values('participants', 'participants_sum', 'start')
如有任何建议,我们将不胜感激!
非常感谢@endre-both his/her 帮助我解决了更大的问题。
最终结果我想要每个 start 和 end transition 的值事件 table 以便我可以确定参与者过多的时间段。但我担心解释起来太复杂了。
这是我最终得到的结果
from django.contrib.gis.db import models
from django.db.models import F, Window, Sum
from django.utils import timezone
overlap_filter_start = Q(start__lte=OuterRef('start'), end__gte=OuterRef('start'))
overlap_filter_end = Q(start__lte=OuterRef('end'), end__gte=OuterRef('end'))
subquery_start = Subquery(Event.objects
.filter(overlap_filter_start)
.annotate(sum_participants=Window(expression=Sum('participants'),))
.values('sum_participants')[:1],
output_field=models.IntegerField()
)
subquery_end = Subquery(Event.objects
.filter(overlap_filter_end)
.annotate(sum_participants=Window(expression=Sum('participants'),))
.values('sum_participants')[:1],
output_field=models.IntegerField()
)
# Will eventually filter the dates I'm checking over specific date ranges rather than the entire Event table
# but for simplicity, filtering from yesterday to tomorrow
before = timezone.now().date() - timezone.timedelta(days=1)
after = timezone.now().date() + timezone.timedelta(days=1)
events_start = Event.objects.filter(start__date__lte=after, start__date__gte=before).annotate(simultaneous_participants=subquery_start)
events_end = Event.objects.filter(end__date__lte=after, end__date__gte=before).annotate(simultaneous_participants=subquery_end)
# Here I combine the queries for *start* transition moments and *end* transition moments, and rename the DateTime I'm looking at to *moment*, and make sure to only return distinct moments (since two equal moments will have the same number of participants)
events = events_start.annotate(moment=F('start')).values('moment', 'simultaneous_participants').union(
events_end.annotate(moment=F('end')).values('moment', 'simultaneous_participants')).order_by('moment').distinct()
for event in events:
print(event)
print(events.count())
现在我可以在 Python 中使用生成的相对较小的结果查询集和过程来确定参与者数量在何处过高,以及何时回落到可接受的 table 水平。
可能有更有效的方法来解决这个问题,但我对此非常满意。比尝试在 Python 中完成所有繁重的工作要好得多。
结果输出是这样的:
{'simultaneous_participants': 45, 'moment': datetime.datetime(2019, 3, 23, 7, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 45, 'moment': datetime.datetime(2019, 3, 23, 11, 30, tzinfo=<UTC>)}
{'simultaneous_participants': 40, 'moment': datetime.datetime(2019, 3, 23, 14, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 40, 'moment': datetime.datetime(2019, 3, 23, 15, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 35, 'moment': datetime.datetime(2019, 3, 23, 16, 30, tzinfo=<UTC>)}
{'simultaneous_participants': 85, 'moment': datetime.datetime(2019, 3, 24, 19, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 125, 'moment': datetime.datetime(2019, 3, 25, 12, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 90, 'moment': datetime.datetime(2019, 3, 25, 12, 30, tzinfo=<UTC>)}
{'simultaneous_participants': 135, 'moment': datetime.datetime(2019, 3, 25, 13, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 110, 'moment': datetime.datetime(2019, 3, 25, 18, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 160, 'moment': datetime.datetime(2019, 3, 25, 19, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 160, 'moment': datetime.datetime(2019, 3, 25, 20, 30, tzinfo=<UTC>)}
{'simultaneous_participants': 115, 'moment': datetime.datetime(2019, 3, 25, 22, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 80, 'moment': datetime.datetime(2019, 3, 25, 23, 30, tzinfo=<UTC>)}
14
要使用基于单个事件的某些条件过滤的聚合来注释您的 Events
,每个事件需要单独的子查询。
此过滤器应该有助于查找与特定时间范围重叠的所有事件:
overlap_filter = Q(start__lte=OuterRef('end'), end__gte=OuterRef('start'))
这会为您提供在结束时间之前或结束时间开始并在开始时间或之后结束的所有事件。过滤器将在子查询中使用,并且 OuterRef
我们引用外部查询中的字段。
接下来,子查询。从子查询中获取聚合是 unexpectedly difficult,因为聚合不是惰性的(=它们会立即执行)并且需要 Subquery
。一种解决方法是使用 Window
:
subquery = Subquery(Event.objects
.filter(overlap_filter)
.annotate(sum_participants=Window(Sum('participants'),))
.values('sum_participants')[:1],
output_field=IntegerField()
)
最后,带注释的查询 Events
:
events = Event.objects.annotate(simultaneous_participants=subquery)
请注意,虽然此计数中的参与者与我们正在查看的 Event
重叠,但他们不一定 彼此重叠 – 他们是在 Event
期间的某个时间 所有人都在场,但并非所有人都在同一时间出现——有些人可能会在其他人到达之前离开。要计算实际出勤高峰,您需要查看较小的时间增量(取决于开始和结束时间的交错方式)。
我有一个带有开始日期时间和结束日期时间以及参与者人数的事件模型。
对于每个事件对象,我想获得与开始日期时间重叠的任何事件中所有参与者的注释总和。这样我就可以确保在任何给定时间没有太多参与者。
class Event(models.Model):
start = models.DateTime()
end = models.DateTime()
participants = models.IntegerField()
我一直在阅读有关 Window 函数的信息,也许它可以在这里工作,但我无法正确理解。
我试过了,但它不起作用,因为它希望将具有相同开始日期时间的事件组合在一起,而不是将开始和结束日期时间与原始事件开始日期时间重叠。
starts = Event.objects.annotate(
participants_sum=Window(
expression=Sum('participants'),
partition_by=[F('start'),],
order_by=ExtractDay('start').asc(),
),
).values('participants', 'participants_sum', 'start')
如有任何建议,我们将不胜感激!
非常感谢@endre-both his/her 帮助我解决了更大的问题。
最终结果我想要每个 start 和 end transition 的值事件 table 以便我可以确定参与者过多的时间段。但我担心解释起来太复杂了。
这是我最终得到的结果
from django.contrib.gis.db import models
from django.db.models import F, Window, Sum
from django.utils import timezone
overlap_filter_start = Q(start__lte=OuterRef('start'), end__gte=OuterRef('start'))
overlap_filter_end = Q(start__lte=OuterRef('end'), end__gte=OuterRef('end'))
subquery_start = Subquery(Event.objects
.filter(overlap_filter_start)
.annotate(sum_participants=Window(expression=Sum('participants'),))
.values('sum_participants')[:1],
output_field=models.IntegerField()
)
subquery_end = Subquery(Event.objects
.filter(overlap_filter_end)
.annotate(sum_participants=Window(expression=Sum('participants'),))
.values('sum_participants')[:1],
output_field=models.IntegerField()
)
# Will eventually filter the dates I'm checking over specific date ranges rather than the entire Event table
# but for simplicity, filtering from yesterday to tomorrow
before = timezone.now().date() - timezone.timedelta(days=1)
after = timezone.now().date() + timezone.timedelta(days=1)
events_start = Event.objects.filter(start__date__lte=after, start__date__gte=before).annotate(simultaneous_participants=subquery_start)
events_end = Event.objects.filter(end__date__lte=after, end__date__gte=before).annotate(simultaneous_participants=subquery_end)
# Here I combine the queries for *start* transition moments and *end* transition moments, and rename the DateTime I'm looking at to *moment*, and make sure to only return distinct moments (since two equal moments will have the same number of participants)
events = events_start.annotate(moment=F('start')).values('moment', 'simultaneous_participants').union(
events_end.annotate(moment=F('end')).values('moment', 'simultaneous_participants')).order_by('moment').distinct()
for event in events:
print(event)
print(events.count())
现在我可以在 Python 中使用生成的相对较小的结果查询集和过程来确定参与者数量在何处过高,以及何时回落到可接受的 table 水平。
可能有更有效的方法来解决这个问题,但我对此非常满意。比尝试在 Python 中完成所有繁重的工作要好得多。
结果输出是这样的:
{'simultaneous_participants': 45, 'moment': datetime.datetime(2019, 3, 23, 7, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 45, 'moment': datetime.datetime(2019, 3, 23, 11, 30, tzinfo=<UTC>)}
{'simultaneous_participants': 40, 'moment': datetime.datetime(2019, 3, 23, 14, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 40, 'moment': datetime.datetime(2019, 3, 23, 15, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 35, 'moment': datetime.datetime(2019, 3, 23, 16, 30, tzinfo=<UTC>)}
{'simultaneous_participants': 85, 'moment': datetime.datetime(2019, 3, 24, 19, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 125, 'moment': datetime.datetime(2019, 3, 25, 12, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 90, 'moment': datetime.datetime(2019, 3, 25, 12, 30, tzinfo=<UTC>)}
{'simultaneous_participants': 135, 'moment': datetime.datetime(2019, 3, 25, 13, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 110, 'moment': datetime.datetime(2019, 3, 25, 18, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 160, 'moment': datetime.datetime(2019, 3, 25, 19, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 160, 'moment': datetime.datetime(2019, 3, 25, 20, 30, tzinfo=<UTC>)}
{'simultaneous_participants': 115, 'moment': datetime.datetime(2019, 3, 25, 22, 0, tzinfo=<UTC>)}
{'simultaneous_participants': 80, 'moment': datetime.datetime(2019, 3, 25, 23, 30, tzinfo=<UTC>)}
14
要使用基于单个事件的某些条件过滤的聚合来注释您的 Events
,每个事件需要单独的子查询。
此过滤器应该有助于查找与特定时间范围重叠的所有事件:
overlap_filter = Q(start__lte=OuterRef('end'), end__gte=OuterRef('start'))
这会为您提供在结束时间之前或结束时间开始并在开始时间或之后结束的所有事件。过滤器将在子查询中使用,并且 OuterRef
我们引用外部查询中的字段。
接下来,子查询。从子查询中获取聚合是 unexpectedly difficult,因为聚合不是惰性的(=它们会立即执行)并且需要 Subquery
。一种解决方法是使用 Window
:
subquery = Subquery(Event.objects
.filter(overlap_filter)
.annotate(sum_participants=Window(Sum('participants'),))
.values('sum_participants')[:1],
output_field=IntegerField()
)
最后,带注释的查询 Events
:
events = Event.objects.annotate(simultaneous_participants=subquery)
请注意,虽然此计数中的参与者与我们正在查看的 Event
重叠,但他们不一定 彼此重叠 – 他们是在 Event
期间的某个时间 所有人都在场,但并非所有人都在同一时间出现——有些人可能会在其他人到达之前离开。要计算实际出勤高峰,您需要查看较小的时间增量(取决于开始和结束时间的交错方式)。