Django-用与该事件的开始日期时间重叠的参与者总数来注释每个事件

时间:2019-03-24 07:42:30

标签: django annotations django-queryset window-functions

我有一个Event模型,其中包含一个开始日期时间和一个结束日期时间,以及参与者的数量。

对于每个Event对象,我想获得在与起始DateTime重叠的任何事件中所有参与者的带注释的总和。这样可以确保在任何给定时间没有太多参与者。

class Event(models.Model):
    start = models.DateTime()
    end = models.DateTime()
    participants = models.IntegerField()

我一直在阅读有关Window函数的信息,也许可以在这里使用,但我做对了。

我尝试过此操作,但是它不起作用,因为它希望将事件与SAME开始日期时间分组在一起,而不是将开始和结束日期时间段与原始事件开始日期时间段重叠。

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的帮助,我才得以解决更大的问题。

最终结果是我想要事件表中每个 start end transition 的值,以便可以确定时间段参加者太多。但是我担心解释起来太复杂了。

这就是我最后得到的

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中使用相对较小的结果查询集和过程来确定参与者数量过高的地方以及何时下降到可接受的水平。

也许有一种更有效的方法来解决此问题,但是我对此非常满意。比尝试在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

1 个答案:

答案 0 :(得分:1)

要使用根据个别事件根据某些条件过滤的汇总注释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期间的某个时间,但并非所有对象都同时出现–有些可能在其他对象到达之前就离开了。要计算实际的出勤高峰,您需要查看较小的时间增量(取决于开始时间和结束时间如何交错)。