【问题标题】:How to calculate count in Django through two m2m levels?如何通过两个 m2m 级别计算 Django 中的计数?
【发布时间】:2020-06-18 10:08:26
【问题描述】:

我有 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)
)

使用 distinct 可以正常工作,但速度很慢。

我尝试过使用子查询。

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

但它返回异常“用作表达式的子查询返回的不止一行”

有什么方法可以更快地计算或修复第二个解决方案?因为我对向后相关的模型做了类似的事情,而且效果很好而且很快。

提前感谢您的帮助。

【问题讨论】:

    标签: django count subquery annotate m2m


    【解决方案1】:

    您可以在 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()
    
    

    然后,如果您有一个客户实例,例如john,您可以调用john.count_hobbies 来获取该客户的爱好数量。

    【讨论】:

    • 感谢您的回答。我正在考虑添加属性,但我认为它不会更快,因为我需要在许多实例的管理更改列表中拥有这个字段,所以每个实例都会有单独的请求,而不是一个请求。但是对于更新/详细信息页面来说很好。
    • AFAIK,每个 Customer 实例都可以调用属性字段。我在这里错过了什么吗?
    • 我已经检查了您的解决方案。它对 db 进行更多查询,但运行速度更快。谢谢。
    • 很好,很高兴听到这个消息。
    【解决方案2】:

    我认为您的子查询几乎是正确的,只是您的值之一需要不同才能让 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')
    )
    

    【讨论】:

    • 谢谢你的答案,需要检查一下。
    最近更新 更多