Django - аннотирование каждого события суммой участников, которые совпадают с датой начала этого события - PullRequest
1 голос
/ 24 марта 2019

У меня есть модель события с начальным DateTime и конечным DateTime, а также количеством участников.

Для каждого объекта Event я хочу получить аннотированную сумму ВСЕХ участников в любом событии, котороеперекрывается с началом DateTime.Это так, чтобы я мог убедиться, что не слишком много участников в любой момент времени.

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

Я читал о функциях Window, и, возможно, это сработало бы здесь, но я не могу получитьэто правильно.

Я пробовал это, но это не работает, потому что он пытается сгруппировать события с ОДНОМ ТОЧКОМ начала DateTime, а не перекрывать периоды времени начала и окончания с исходным событием начала DateTime.

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 с его / ее помощью, я смог решить большую проблему.

конечный результат Мне нужны значения каждого начала и конца перехода в моей таблице событий, чтобы я мог определить периоды времени при слишком большом количестве участников.Но я волновался, что объяснить это будет слишком сложно.

Вот что я закончил с

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 Ответ

1 голос
/ 24 марта 2019

Чтобы аннотировать Events с помощью агрегата, отфильтрованного по некоторым критериям на основе отдельного события, вам нужны отдельные подзапросы на событие.

Этот фильтр должен помочь найти все события, которые перекрываются с определенным временемдиапазон:

overlap_filter = Q(start__lte=OuterRef('end'), end__gte=OuterRef('start'))

Получает все события, которые начинаются до или в конечное время и заканчиваются в или после начального времени.Фильтр будет использоваться в подзапросе, и с помощью OuterRef мы ссылаемся на поля во внешнем запросе.

Далее, подзапрос. неожиданно трудно получить агрегат из подзапроса, так как агрегаты не ленивы (= они выполняются немедленно), и 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, но не всеиз них в одно и то же время - некоторые могут уйти до прибытия других.Чтобы рассчитать фактические пики посещаемости, вам нужно взглянуть на меньшие приращения времени (в зависимости от того, как начальное и конечное время находятся в шахматном порядке).

...