Django: аннотирование модели с несколькими графами происходит очень медленно

Я пытаюсь аннотировать модель, которая имеет несколько отношений, с несколькими подсчетами этих отношений. Но запрос выполняется очень медленно.

Campaign.objects.annotate(
    num_characters=Count("character", distinct=True),
    num_factions=Count("faction", distinct=True),
    num_locations=Count("location", distinct=True),
    num_quests=Count("quest", distinct=True),
    num_loot=Count("loot", distinct=True),
    num_entries=Count("entry", distinct=True),
)

Когда я говорю о сверхмедленной работе, я имею в виду это: это занимает несколько минут на моем локальном MacBook Pro с M1 Max 😰 И в этих таблицах даже не так много строк.

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

campaigns = Campaign.objects.all()
for campaign in campaigns:
    campaign.num_characters = campaign.character_set.count()
    campaign.num_factions = campaign.faction_set.count()
    campaign.num_locations = campaign.location_set.count()
    campaign.num_quests = campaign.quest_set.count()
    campaign.num_loot = campaign.loot_set.count()
    campaign.num_entries = campaign.entry_set.count()

Но при этом, конечно, выполняется много запросов, что тоже не идеально. Нельзя ли как-то оптимизировать этот запрос?

Хотя это немного некрасиво, вы должны быть в состоянии ускорить запрос, используя подзапросы вместо этого

from django.db.models import OuterRef, Subquery, Count
Campaign.objects.annotate(
    num_characters=Subquery(Character.objects.filter(campaign=OuterRef('pk')).order_by().values('campaign').annotate(count=Count('campaign')).values('count')),
    num_factions=Subquery(Faction.objects.filter(campaign_id=OuterRef('pk')).order_by().values('campaign').annotate(count=Count('campaign')).values('count')),
    num_locations=Subquery(Location.objects.filter(campaign_id=OuterRef('pk')).order_by().values('campaign').annotate(count=Count('campaign')).values('count')),
    num_quests=Subquery(Quest.objects.filter(campaign_id=OuterRef('pk')).order_by().values('campaign').annotate(count=Count('campaign')).values('count')),
    num_loot=Subquery(Loot.objects.filter(campaign_id=OuterRef('pk')).order_by().values('campaign').annotate(count=Count('campaign')).values('count')),
    num_entries=Subquery(Entry.objects.filter(campaign_id=OuterRef('pk')).order_by().values('campaign').annotate(count=Count('campaign')).values('count')),
)
Вернуться на верх