如何在Django中从多对多中间模型中选择?

2 投票
4 回答
1694 浏览
提问于 2025-04-17 09:13

我有书和人的模型:

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 个回答

1

你可以很简单地获取所有作者的ID,方法是使用 Book.objects.all().values_list('author', flat=True).distinct()

正如jpic在下面提到的

Person.objects.filter(pk__in=Book.objects.values_list('author').distinct()) 这个方法会给你所有的人员对象,而不仅仅是他们的ID。

3

最简单的方法:

Person.objects.filter(book__isnull=False)

这样做会选出所有至少有一本书的人。

3

就像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概念:

  1. 注解是对查询集每个值进行聚合。如果你使用aggregate(Count('book')),那么你会得到书籍的总数。如果你使用annotate(Count('book')),那么你会得到每个查询集值(每个人)的书籍总数。而且,每个人都有一个'count_book'属性,这非常酷:Person.objects.annotate(count_book=Count('book')).filter(count_book__gt=0)[0].count_book

  2. 子查询非常有用,可以用来创建复杂的查询或优化查询(例如合并查询集、预取通用关系等)。

撰写回答