Django如何查询ManyToManyField下所有关联书籍的唯一作者QuerySet
实现方案
直接通过Django ORM的跨关联查询即可实现需求,不需要手写原生SQL,以下是两种生产环境常用的可靠写法:
方案1:反向关联过滤(最常用、最灵活)
# 未自定义多对多反向关联名时使用该写法 authors_with_books = Author.objects.filter(book__isnull=False).distinct()
实现逻辑:
- Django会自动为多对多关系生成反向关联,未指定
related_name参数时,Author侧的反向关联名默认为book book__isnull=False会自动跨多对多中间表做关联查询,筛选出至少关联了1本Book的作者- 末尾的
distinct()用于去除join过程中产生的重复行(比如一个作者关联多本书时会生成多条重复的作者记录),最终返回完全去重的作者QuerySet
如果你在定义Book模型的多对多字段时自定义了related_name,比如:
class Book(models.Model): author = models.ManyToManyField(Author, related_name="books")
把查询字段替换成你自定义的反向关联名即可:
authors_with_books = Author.objects.filter(books__isnull=False).distinct()
这个写法支持灵活叠加筛选条件,比如你需要筛选「2024年出版的书籍关联的作者」,直接追加条件即可:
authors_from_2024_books = Author.objects.filter( book__publish_time__year=2024 ).distinct()
方案2:子查询收敛(大数据量场景性能更优)
如果你的表数据量在百万级以上,担心多表join产生性能问题,可以先从Book表收敛所有关联的作者ID,再过滤Author表:
# 取出所有被书籍关联的作者ID,提前去重 related_author_ids = Book.objects.values_list("author", flat=True).distinct() # 过滤得到最终作者QuerySet,主键查询天然去重,不需要额外加distinct authors_with_books = Author.objects.filter(id__in=related_author_ids)
这个写法的查询逻辑会先生成一个仅查作者ID的子查询,避免大表直接join产生的笛卡尔积问题,数据量越大性能优势越明显。
结果验证
对应你给出的示例场景:
- BookA关联AuthorA、AuthorB
- BookB关联AuthorB、AuthorC
- AuthorD、AuthorE、AuthorF无关联书籍
两种写法返回的QuerySet均只会包含AuthorA、AuthorB、AuthorC三个实例,不会出现重复作者,也不会包含未关联书籍的作者,完全符合预期。
之前你查到的单书作者统计方案,本质是从Book维度做聚合统计,查询维度不对自然无法拿到跨书籍的全量去重作者,只要把查询维度切换到Author侧,判断关联书籍的存在性即可解决问题。
内容的提问来源于stack exchange,提问作者QSBK
相关产品推荐
相关产品推荐

