如何在Django中从多对多中间模型中选择?
我有书和人的模型:
from django.db import models
class Book(models.Model):
author = models.ManyToManyField('Person')
class Person(models.Model):
name = models.CharField(max_length=16)
我在这里简化了一下。请问我该如何写一个Django查询,来获取所有书的作者?如果用SQL,我会在中间表上做一个选择,然后把它和人表连接起来,以获取名字,但我不太确定在这里怎么做类似的事情……当然,Person表里有些人并不是书的作者,或者我可以直接用Person.objects.all()来获取所有人。
4 个回答
你可以很简单地获取所有作者的ID,方法是使用
Book.objects.all().values_list('author', flat=True).distinct()
正如jpic在下面提到的
Person.objects.filter(pk__in=Book.objects.values_list('author').distinct())
这个方法会给你所有的人员对象,而不仅仅是他们的ID。
最简单的方法:
Person.objects.filter(book__isnull=False)
这样做会选出所有至少有一本书的人。
就像1、2、3一样简单,关于在注解上过滤:
from django.db.models import Count
Person.objects.annotate(count_book=Count('book')).filter(count_book__gt=0)
出于好奇,我生成了这个话题中提到的每种方法的SQL语句:
In [9]: Person.objects.annotate(count_book=Count('book')).filter(count_book__gt=0)
DEBUG (0.000) SELECT "testapp_person"."id", "testapp_person"."name", COUNT("testapp_book_author"."book_id") AS "count_book" FROM "testapp_person" LEFT OUTER JOIN "testapp_book_author" ON ("testapp_person"."id" = "testapp_book_author"."person_id") GROUP BY "testapp_person"."id", "testapp_person"."name", "testapp_person"."id", "testapp_person"."name" HAVING COUNT("testapp_book_author"."book_id") > 0 LIMIT 21; args=(0,)
Out[9]: [<Person: Person object>]
In [10]: Person.objects.exclude(book=None)
DEBUG (0.000) SELECT "testapp_person"."id", "testapp_person"."name" FROM "testapp_person" WHERE NOT (("testapp_person"."id" IN (SELECT U0."id" FROM "testapp_person" U0 LEFT OUTER JOIN "testapp_book_author" U1 ON (U0."id" = U1."person_id") LEFT OUTER JOIN "testapp_book" U2 ON (U1."book_id" = U2."id") WHERE (U2."id" IS NULL AND U0."id" IS NOT NULL)) AND "testapp_person"."id" IS NOT NULL)) LIMIT 21; args=()
Out[10]: [<Person: Person object>]
In [11]: Person.objects.filter(pk__in=Book.objects.values_list('author').distinct())
DEBUG (0.000) SELECT "testapp_person"."id", "testapp_person"."name" FROM "testapp_person" WHERE "testapp_person"."id" IN (SELECT DISTINCT U1."person_id" FROM "testapp_book" U0 LEFT OUTER JOIN "testapp_book_author" U1 ON (U0."id" = U1."book_id")) LIMIT 21; args=()
Out[11]: [<Person: Person object>]
也许这能帮助你做出选择。
就我个人而言,我更喜欢Chris的方法,因为它是最简短的。不过,我不太确定使用子查询会有什么影响,而另外两种方法都是用到了子查询。话虽如此,它们确实展示了一些有趣的QuerySet概念:
注解是对查询集每个值进行聚合。如果你使用aggregate(Count('book')),那么你会得到书籍的总数。如果你使用annotate(Count('book')),那么你会得到每个查询集值(每个人)的书籍总数。而且,每个人都有一个'count_book'属性,这非常酷:
Person.objects.annotate(count_book=Count('book')).filter(count_book__gt=0)[0].count_book
子查询非常有用,可以用来创建复杂的查询或优化查询(例如合并查询集、预取通用关系等)。