Как рассчитать количество в Django через два уровня m2m? - PullRequest
1 голос
/ 05 марта 2020

У меня есть 3 модели, такие как:

class Customer(models.Model):
    hobby_groups=models.ManyToManyField(
        'HobbyGroup',
        blank=True,
        related_name='group_customers',
    )


class HobbyGroup(models.Model):
    hobbies = models.ManyToManyField(
        'Hobby',
        blank=True,
        related_name='hobby_groups',
    )

class Hobby(models.Model):
    title = models.CharField(max_length=255, default='football')

И мне нужно рассчитать количество хобби для каждого клиента.

qs = Customer.objects.annotate(
    hobbies_count=Count('hobby_groups__hobbies', distinct=True)
)

При отличном он работает нормально, но очень медленно.

Я пытался использовать подзапрос.

hobbies = Hobby.objects.filter(hobby_groups__group_customers=OuterRef('pk')).values('pk')
hobbies_count = hobbies.annotate(count=Count('*')).values('count')
qs = Customer.objects.annotate(
    hobbies_count=Subquery(hobbies_count)
)

Но он возвращает исключение 'Более одной строки, возвращенной подзапросом, использованным в качестве выражения'

Есть ли способ посчитать это быстрее или исправить второе решение? Потому что я делал то же самое для отсталых моделей, и все работало нормально и быстро.

Заранее спасибо за помощь.

Ответы [ 2 ]

0 голосов
/ 25 марта 2020

Я думаю, что ваш подзапрос почти правильный, только одно из ваших значений должно отличаться, чтобы получить Django для генерации правильной группы:

hobbies = Hobby.objects.filter(
    hobby_groups__group_customers=OuterRef('pk')
    ).values('hobby_groups__group_customers')

hobbies_count = hobbies.annotate(count=Count('*')).values('count')

qs = Customer.objects.annotate(
    hobbies_count=Subquery(hobbies_count)
)

В качестве альтернативы, вы можете использовать пакет django - sql -utils и сделать это намного проще:

from sql_util.utils import SubqueryCount

qs = Customer.objects.annotate(
    hobbies_count=SubqueryCount('hobby_groups__hobbies')
)
0 голосов
/ 06 марта 2020

Вы можете добавить пользовательское свойство в вашей модели Customer для подсчета.

class Customer(models.Model):
    hobby_groups=models.ManyToManyField(
        'HobbyGroup',
        blank=True,
        related_name='group_customers',
    )

    @property
    def count_hobbies(self):
        return Hobby.objects.filter(
            hobby_groups__group_customers=self
        ).distinct().count()

Затем, если у вас есть экземпляр Customer, например, john, вы можете вызвать john.count_hobbies, чтобы получить количество хобби этого клиента.

...