Django中按外键去重获取单个关联Answer对象的QuerySet方法
问题:按Survey外键去重获取Answer QuerySet
模型定义
class Answer(models.Model): survey = models.ForeignKey(Survey)
需求
根据Survey外键获取Answer的QuerySet,要求每个Survey仅对应一个Answer对象。例如现有3个Answer对象:
answers = [ {"survey": 1}, {"survey": 2}, {"survey": 1}, ]
期望返回的QuerySet为:
[ {"survey": 2}, {"survey": 1}, ]
即同一外键对应的Answer仅保留一个。
尝试过的无效方法
def get_queryset(self, request): qs = super().get_queryset(request) idx = list(qs.values_list("survey_id", flat=True).distinct()) return qs.filter(survey_id__in=idx).distinct()
已实现的方案(寻求优化)
def get_queryset(self, request): qs = super().get_queryset(request) idx = list(qs.values_list("survey_id", flat=True).distinct()) data = [] for i in qs: if i.survey_id in idx: data.append(i) idx.remove(i.survey_id) # 转换为QuerySet return qs.filter(pk__in=[i.pk for i in data])
优化方案建议
方案1:PostgreSQL专属distinct_on(高效简洁)
如果使用PostgreSQL数据库,可以直接利用distinct_on特性按survey_id去重,保留每个Survey对应的第一条记录(可通过排序字段控制保留哪一条,比如按创建时间):
def get_queryset(self, request): qs = super().get_queryset(request) # 按survey_id分组去重,保留每个survey的第一个记录(可调整order_by字段选择保留的对象) return qs.order_by('survey_id', 'id').distinct('survey_id')
方案2:跨数据库通用的Subquery方式
如果需要兼容多数据库,可以用Subquery获取每个Survey对应的最新/最早Answer:
from django.db.models import Subquery, OuterRef def get_queryset(self, request): qs = super().get_queryset(request) # 子查询获取每个survey对应的最新Answer的id(按id倒序取第一个) latest_answer_ids = Answer.objects.filter(survey=OuterRef('survey')).order_by('-id').values('id')[:1] # 过滤出这些目标Answer return qs.filter(id__in=Subquery(latest_answer_ids))
方案3:优化现有遍历逻辑
如果不想修改查询逻辑,也可以优化现有代码,避免不必要的内存开销和列表操作:
def get_queryset(self, request): qs = super().get_queryset(request) # 用字典推导式快速获取每个survey_id对应的一个Answer id(按遍历顺序取第一个) unique_answer_map = {ans.survey_id: ans.id for ans in qs.order_by('survey_id')} # 过滤出这些id对应的Answer return qs.filter(id__in=unique_answer_map.values())
这个方法通过字典的键唯一性自动去重,比原方案的列表遍历+移除操作更高效。
内容的提问来源于stack exchange,提问作者Iqra Moazzama
相关产品推荐
相关产品推荐

