Django筛选中如何高效获取每组值对应的首个对象?
问题分析与解决方案
环境与模型
运行基于PostgreSQL的Django 1.29应用,模型定义如下:
class DataFile(models.Model): user = models.ForeignKey('data.User', verbose_name='Data View') period = models.ForeignKey('data.Period', verbose_name='Period') category = models.ForeignKey('data.Period', verbose_name='Period') created = models.DateTimeField(auto_now_add=True,null=True)
原低效实现
原代码通过循环遍历pending_files中的(user_id, category_id, period_id)组合,获取每组内创建时间最晚且ID在pending_ids中的对象,但循环导致效率极低:
pending_ids = pending_files.values_list("id", flat=True).distinct() my_data = pending_files.values_list("user_id","category_id","period_id") import_files_pending=[] for u, c, p in pending_bu_way_period: last_file_bpw = DataFile.objects.filter(user_id=u, period_id=p,category_id=c).order_by('-created').first() if last_file_bpw and last_file_bpw.id in pending_ids: import_files_pending.append(last_file_bpw)
重构后的错误实现
尝试重构后返回大量非预期对象,代码如下:
import_files_pending2 = DataFile.objects.filter(id__in=pending_ids,user_id=my_data.values_list("user_id", flat=True), category_id__in=my_data.values_list("category_id", flat=True), period_id__in=my_data.values_list("period_id", flat=True)).order_by('user_id', 'period_id', '-created').distinct("from_who_id", 'period_id')
错误原因
- 过滤条件逻辑错误:原逻辑是匹配
(user_id, category_id, period_id)的组合,但重构代码用三个独立的__in条件,会匹配任意user_id在列表、任意category_id在列表、任意period_id在列表的交叉组合,比如原数据只有(u1,c1,p1),重构后会匹配(u1,c2,p1)这类不存在的组合,导致结果范围大幅扩大。 distinct参数错误:代码中distinct("from_who_id", 'period_id')里的from_who_id并非模型字段,且未包含category_id,无法按原逻辑的三元组分组;同时PostgreSQL的DISTINCT ON要求order_by的前缀必须与distinct的字段完全一致,否则分组逻辑失效。- 未正确关联组合关系:重构代码没有限定只取
pending_files中存在的(user_id, category_id, period_id)组合,而是用三个独立__in,导致无关数据被纳入查询。
正确重构方案
方案1:利用PostgreSQL的DISTINCT ON特性(推荐)
PostgreSQL支持DISTINCT ON语法,可按指定字段分组并取每组第一条数据,需确保order_by前缀与distinct字段一致:
import_files_pending2 = DataFile.objects.filter( id__in=pending_ids, # 仅保留pending_files中存在的三元组组合 user_id__in=my_data.values_list('user_id', flat=True), category_id__in=my_data.values_list('category_id', flat=True), period_id__in=my_data.values_list('period_id', flat=True) ).order_by('user_id', 'category_id', 'period_id', '-created').distinct('user_id', 'category_id', 'period_id')
- 逻辑:先按
user_id、category_id、period_id分组,每组内按created倒序排列,DISTINCT ON会保留每组的第一条数据(即创建时间最晚的)。
方案2:使用子查询获取每组最新ID
通过子查询预先获取每个三元组组合对应的最新ID,再查询这些ID的对象:
from django.db.models import Subquery, OuterRef # 子查询:获取每个(user_id, category_id, period_id)组合下,pending_ids中创建时间最晚的ID latest_file_ids = DataFile.objects.filter( user_id=OuterRef('user_id'), category_id=OuterRef('category_id'), period_id=OuterRef('period_id'), id__in=pending_ids ).order_by('-created').values('id')[:1] # 主查询:筛选符合条件的对象 import_files_pending2 = DataFile.objects.filter( id__in=Subquery(latest_file_ids), user_id__in=my_data.values_list('user_id', flat=True), category_id__in=my_data.values_list('category_id', flat=True), period_id__in=my_data.values_list('period_id', flat=True) )
内容的提问来源于stack exchange,提问作者Kodeeo
相关产品推荐
相关产品推荐

