Django多表查询用distinct仍返回重复结果问题排查
问题分析
当查询关联字段(如CaseProgress.desc)时,Django会执行JOIN操作关联Case与CaseProgress表。若一个Case对应多条CaseProgress记录,JOIN后的结果集中会出现重复的Case实例(每条对应一条CaseProgress记录)。
你使用的distinct("rank", "id")无法解决问题,因为Django的distinct()是基于整个查询返回的所有字段去重的——JOIN后结果包含CaseProgress的字段,即使rank和id相同,不同的CaseProgress记录会让整行数据唯一,因此去重失效。
解决方案
针对这个问题,有两种常用的解决思路:
1. 聚合关联字段后构建搜索向量
使用StringAgg将同一Case下所有CaseProgress.desc的内容合并为单个字符串,再基于这个聚合值构建SearchVector。这样每个Case只会生成一条记录,从根源避免重复:
from django.db.models import StringAgg from django.contrib.postgres.search import SearchVector, SearchRank class MyManager(models.Manager): def search(self, query): # 聚合所有关联的desc字段为单个字符串 aggregated_desc = StringAgg("caseprogress__desc", delimiter=" ") # 构建包含主表字段和聚合字段的搜索向量 search_vector = SearchVector("title", weight="A") + SearchVector(aggregated_desc, weight="B") search_rank = SearchRank(search_vector, query) return self.annotate(rank=search_rank)\ .filter(rank__gt=0)\ .order_by("-rank", "-id")
2. 使用子查询获取唯一的Rank值
通过子查询计算每个Case的最高匹配Rank,主查询仅基于Case表获取结果,避免JOIN带来的重复:
from django.db.models import OuterRef, Subquery from django.contrib.postgres.search import SearchVector, SearchRank class MyManager(models.Manager): def search(self, query): # 子查询:计算单个Case的最高匹配Rank rank_subquery = self.filter(id=OuterRef("id"))\ .annotate( vector=SearchVector("title", weight="A") + SearchVector("caseprogress__desc", weight="B"), case_rank=SearchRank(vector, query) )\ .values("case_rank")\ .order_by("-case_rank")[:1] # 主查询:获取带唯一Rank的Case实例 return self.annotate(rank=Subquery(rank_subquery))\ .filter(rank__gt=0)\ .order_by("-rank", "-id")\ .distinct()
关键注意点
- 若不需要保留
CaseProgress的具体记录,优先使用聚合字段方案,性能更优。 - 若需同时获取
CaseProgress数据,可在主查询后追加prefetch_related("caseprogress"),避免N+1查询的同时不影响主查询的去重逻辑。
内容的提问来源于stack exchange,提问作者Viktor
相关产品推荐
相关产品推荐

