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()