Django: аннотируйте запрос одной модели подсчетом разных несвязанных моделей, которые фильтруются по полю первой модели
Длинное название вкратце : У меня есть сложный аннотационный запрос, с которым нужно работать. Примеры моделей:
class FirstModel(models.Model):
master_tag = models.CharField()
... other fields
class SecondModel(models.Model):
ref_name = models.CharField()
Я хочу получить все объекты из FirstModel с подсчетом всех объектов из SecondModel, если ref_name этих объектов совпадает с master_tag объекта FirstModel.
Что я пробовал:
Я пробовал использовать аннотацию с Subquery и OuterRef, но не могу заставить ее работать, так как получаю постоянные ошибки.
from django.db.models import OuterRef, Subquery, Count
sub_query = Subquery(
SecondModel.objects.filter(ref_name=OuterRef("master_tag")).values_list("pk", flat=True)
)
FirstModel.objects.annotate(sm_count=Count(sub_query))
Это дало ошибку: "django.db.utils.ProgrammingError: более одной строки возвращено подзапросом, используемым в качестве выражения". Я попробовал много других вещей, одна из которых - поместить ".count()" в конец подзапроса, но это вызывает другую ошибку, поскольку count пытается оценить запрос с нетерпением и терпит неудачу из-за OuterRef.
Так есть
ли способ получить такой запрос с аннотацией count? Есть ли какие-нибудь глупые ошибки, которые я допустил при написании вышеприведенного запроса?Я некоторое время пытался решить эту проблему с помощью OuterRef и Subquery, но основная проблема заключается в том, что выражения Subquery предназначены для возврата одного значения, а в данном случае нужно вернуть много.
Django, похоже, не поддерживает прямую аннотацию набора запросов с количеством несвязанных экземпляров модели на основе сравнения полей.
Мне удалось добиться желаемого только без Subquery, используя вместо него RawSQL:
from django.db.models.expressions import RawSQL
# Annotate FirstModel queryset with RawSQL
qs = FirstModel.objects.annotate(
sm_count=RawSQL(
"""
SELECT COUNT(*) FROM yourapp_secondmodel
WHERE yourapp_secondmodel.ref_name = yourapp_firstmodel.master_tag
""",
[]
)
)
# Check the sm_count values
qs.values('master_tag','sm_count').order_by('-sm_count')
Другим вариантом может быть итерация по всем экземплярам FirstModel с подсчетом времени появления ref_name, но это настолько неэффективно, что возможно только в небольших наборах данных...
В заключение: все было бы гораздо проще, если бы между этими моделями существовал внешний ключ.
Я буду рад, если кто-то докажет, что я не прав, если кто-то сможет заставить это работать с Subquery, но тем временем RawSQL может решить эту проблему для вас, и он по-прежнему будет возвращать набор запросов.