Django - смешивание двух аннотированных значений в одно с помощью Case/When приводит к тому, что производительность становится медленной на многих элементах

В функции get_queryset внутри набора представлений я создал набор запросов, который должен перечислить большое количество "Курсов", но с аннотацией некоторых подсчетов дочерней модели "КурсПерсона".

У него может быть около 10000 объектов "Course" с общим количеством ~5 миллионов объектов "CoursePerson".

Я хочу аннотировать "interact_count", принимая другое аннотированное значение в зависимости от типа Course. Если я убираю аннотацию "interact_count", то django + postgresql берет ~20-50 мс для ответа. Но когда я возвращаю эту аннотацию обратно, это занимает около 600-800 мс. Я почти уверен, что причиной этой задержки является Case/When. Но я не знаю, как сделать по-другому, потому что мне нужен только один счетчик. (А не 2)

Я не хочу делать это с помощью python, потому что я потеряю упорядочивание или пагинацию.

В этой части и заключается проблема: Как я могу сделать то же самое, но более эффективно?

interact_count=Case(
  When(
    Q(type=models.Course.TypeChoices.PHISHING)
     | Q(type=models.Course.TypeChoices.SMS),
    then=course_person_open_subquery,
  ),
  default=course_person_extra_subquery,
 )

полный набор запросов:

course_person_open_subquery = Subquery(
    CoursePerson.objects.filter(
        course_id=OuterRef('uid'),
        status__gte=models.CoursePerson.StatusChoices.OPEN
    )
    .annotate(count=Func(F('uid'), function="Count"))
    .values('count'),
    output_field=IntegerField()
)

course_person_extra_subquery = Subquery(
    CoursePerson.objects.filter(
        course_id=OuterRef('uid'),
        status__gt=models.CoursePerson.StatusChoices.OPEN
    )
    .annotate(count=Func(F('uid'), function="Count"))
    .values('count'),
    output_field=IntegerField()
)


return models.Course.objects.annotate(
    interact_count=Case(
        When(
            Q(type=models.Course.TypeChoices.PHISHING)
            | Q(type=models.Course.TypeChoices.SMS),
            then=course_person_open_subquery,
        ),
        default=course_person_extra_subquery,
    ),
).all()

Здесь нужно использовать только функцию Count, а не подзапросы. Вы можете передать ей filter kwargs для более точной настройки способа подсчета связанных моделей, которые вы подсчитываете. Ваши подзапросы Case/When + можно заменить более сложным фильтром с использованием объектов Q, переданных в функцию Count.

Думаю, это должно работать:

phishing_and_sms_filter = Q(
    type__in=[
        models.Course.TypeChoices.PHISHING,
        models.Course.TypeChoices.SMS
    ],
    courseperson__status__gte=models.CoursePerson.StatusChoices.OPEN
)
default_filter = Q(
    courseperson__status__gt=models.CoursePerson.StatusChoices.OPEN
)

models.Course.objects.annotate(
    interact_count=Count(
        'courseperson',
        filter=Q(phishing_and_sms_filter | default_filter)
    )
).all()
Вернуться на верх