Django多对多关系查询去重问题:order_by+distinct仍产生重复结果
嘿,我太懂你这种卡了好几天的烦躁感!你用objects.order_by("relation").distinct()处理多对多关联查询时,结果总是因为关联关系的数量重复出现,核心问题在于多对多是通过中间表(也就是你的GroupMember)来关联的,直接按关联字段排序+去重,本质上是保留了每条中间表记录对应的主模型实例,所以才会重复。
先把你没写完的模型补全(假设GroupMember有个join_date字段,用来表示加入时间,毕竟你说的"relation"应该是中间表的某个排序字段):
class Person(models.Model): name = models.CharField(max_length=200) groups = models.ManyToManyField('Group', through='GroupMember', related_name='people') class Meta: ordering = ['name'] def __unicode__(self): return self.name class Group(models.Model): name = models.CharField(max_length=200) def __unicode__(self): return self.name class GroupMember(models.Model): person = models.ForeignKey(Person, on_delete=models.CASCADE) group = models.ForeignKey(Group, on_delete=models.CASCADE) join_date = models.DateTimeField(auto_now_add=True) # 假设这是你用来排序的"relation"字段
下面给你几种可行的解决方案:
方案一:用Subquery获取每个Person的最新关联记录
这种方法兼容性强,支持所有数据库:
from django.db.models import Subquery, OuterRef # 先获取每个Person最新的GroupMember记录 latest_group_member = GroupMember.objects.filter( person=OuterRef('pk') ).order_by('-join_date')[:1] # 然后查询Person,关联最新的GroupMember people = Person.objects.annotate( latest_group=Subquery(latest_group_member.values('group__name')), latest_join_date=Subquery(latest_group_member.values('join_date')) ).distinct()
这样每个Person只会出现一次,并且带上了最新的关联分组信息。
方案二:用annotate结合Max筛选
如果你的排序字段是日期/数字类型,可以用Max来获取每个Person的最新关联字段值,再过滤:
from django.db.models import Max # 先获取每个Person的最新join_date people_with_latest_join = Person.objects.annotate( latest_join=Max('groupmember__join_date') ) # 再关联中间表,筛选出匹配最新join_date的记录 result = Person.objects.filter( groupmember__join_date=people_with_latest_join.values('latest_join') ).distinct()
方案三:PostgreSQL专属的distinct on
如果你用的是PostgreSQL,可以用distinct on语法,直接按Person去重,保留排序后的第一条(也就是最后一条关联记录):
people = Person.objects.order_by('pk', '-groupmember__join_date').distinct('pk')
这里order_by里先放要去重的字段(Person的pk),再放排序字段(倒序取最新),distinct('pk')会保留每个pk对应的第一条记录,也就是最新的关联那条。
为什么你原来的方法不行?
你之前用的order_by("relation").distinct(),这里的relation如果是中间表的字段,Django会把中间表的每条记录和Person关联起来,生成多条Person实例(每个关联分组对应一条),distinct()只能去掉完全重复的Person记录,但如果Person关联了多个分组,这些记录的关联字段不同,所以不会被去重,导致重复出现。
内容的提问来源于stack exchange,提问作者Sa231asd3

