我有一个用例,我必须计算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。似乎无法理解为什么。
答案 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']>