Django按多对多字段过滤未达预期,单次filter如何实现全匹配?
问题根因
- 第一种写法
MyModel.objects.filter(categories__id__in = [0, 1, 2])本质是OR匹配逻辑:只要实例关联的分类中有任意一个ID落在目标列表中就会被返回,自然会出现缺少某个分类也命中的情况。 - 第二种写法
Q(categories__id = 0) & Q(categories__id = 1) & Q(categories__id = 2)失效的原因是:单轮filter调用中,所有针对多对多关联字段的查询条件,都是作用在同一条中间表关联行上,不存在某一条关联行同时满足分类ID为0、1、2,所以永远不会返回结果。
可行解决方案
我们可以通过聚合统计匹配分类数的方式实现“同时包含所有目标分类”的过滤逻辑,无需多次链式调用filter匹配单个分类(注:后续的二次filter是对聚合统计结果的过滤,不属于你要求禁止的多次匹配分类的filter调用):
- 首先导入Django的聚合函数
Count
from django.db.models import Count
- 执行查询逻辑
target_category_ids = [0, 1, 2] required_match_num = len(target_category_ids) result = Article.objects.filter( categories__id__in=target_category_ids ).annotate( matched_category_count=Count("categories", distinct=True) ).filter( matched_category_count=required_match_num )
逻辑说明
- 先通过
categories__id__in过滤出所有至少命中一个目标分类的实例 - 用
annotate给每个实例统计它关联的分类中,命中目标列表的唯一分类数量 - 最后过滤出统计数等于目标分类总数的实例,这些实例就是同时包含所有目标分类的结果
- 参数
distinct=True是为了避免同一个分类被重复关联的异常数据导致统计计数不准
内容的提问来源于stack exchange,提问作者greg
相关产品推荐
相关产品推荐

