使用条件

时间:2017-04-24 11:53:45

标签: django django-models django-views

我有一个用例,我必须计算ManyToManyField的出现次数,但它比我想的要复杂得多。

models.py

class Tag(models.Model):
    name = models.CharField(max_length=100, unique=True)

class People(models.Model):
    tag = models.ManyToManyField(Tag, blank=True)

在这里,我必须提供一个Tags列表及其整体显示的次数,但仅适用于那些有&gt; 0和<6个标签的People。类似的东西:

tag1 - 265338
tag2 - 4649303
tag3 - 36636
...

这是我最初提出计数的方式:

q = People.objects.annotate(tag_count=Count('tag')).filter(tag_count__lte=6, tag_count__gt=0)    

for tag in Tag.objects.all(): 
    cnt = q.filter(tag__name=tag.name).count()
    # doing something with the cnt

但后来我意识到这可能效率低下,因为我可能多次遍历People模型(人物中的记录比Tag中的记录大)。

直觉上我认为我应该能够对Tag模型进行一次迭代而不需要People模型的任何迭代。所以我想出了这个:

for tag in Tag.objects.all(): 
    cnt = tag.people_set.annotate(tag_count=Count('tag')).filter(tag_count__lte=6).count()
    # doing something with the cnt

但是,首先,这并没有产生预期的结果。其次,我认为这似乎变得更加复杂,所以也许我会使一件简单的事情复杂化。听取任何建议。

更新:我收到了queryset.query并在db上运行查询以进行调试。出于某种原因,结果联接中的tag_count列显示所有1。似乎无法理解为什么。

1 个答案:

答案 0 :(得分:2)

可以使用反向ManyToMany字段查询来完成。

还可以减少开销,并将大部分开销从python转移到数据库服务器。

from some_app.models import Tag, People
from django.db.models import F, Value, Count, CharField
from django.db.models.functions import Concat

# queryset: people with tags >0 and <6, i.e. 1 to 5 tags
people_qualified = People.objects.annotate(tag_count=Count('tag'))\
               .filter(tag_count__range=(1, 5))

# query tags used with above category of people, with count
tag_usage = Tag.objects.filter(people__in=people_qualified)\
            .annotate(tag=F('name'), count=Count('people'))\
            .values('tag', 'count')
# Result: <QuerySet [{'count': 3, 'tag': u'hello'}, {'count': 2, 'tag': u'world'}]>

# similarily, if needed the string output
tag_usage_list = Tag.objects.filter(people__in=people_qualified)\
                .annotate(tags=Concat(F('name'), Value(' - '), Count('people'), 
                                      output_field=CharField()))\
                .values_list('tags', flat=True)
# Result: <QuerySet [u'hello - 3', u'world - 2']>