Django查询:按模型属性分组并保留每组最新记录
Django按project分组获取每个分组最新记录的正确实现
模型定义
class Building(models.Model): building_id = models.AutoField(primary_key=True) created_at = models.DateTimeField(auto_now_add=True, blank=True) project = models.CharField(max_length=200, blank=True, null=True)
现有方案问题
方案1:循环查询生成字典
这种方式生成的字典结构,无法直接用{% for i in projects %}的列表遍历语法在模板中使用,同时处理关联对象的pk时也会有不便。
方案2:全局最新记录
用project__in搭配latest只能拿到全局范围内最新的单条记录,无法实现按project分组获取各自最新记录的需求。
正确实现方法
方法1:改造循环逻辑,生成可遍历列表
把字典结构换成列表,每个元素包含项目名和对应最新记录对象,适配模板遍历需求:
# 排除空项目(可选,根据业务需求调整) projects = Building.objects.exclude(project__isnull=True).values_list('project', flat=True).distinct() latest_buildings = [] for project in projects: latest = Building.objects.filter(project=project).latest('created_at') latest_buildings.append({ 'project_name': project, 'building': latest }) context['latest_buildings'] = latest_buildings
模板中使用示例:
{% for item in latest_buildings %} <p>项目:{{ item.project_name }}</p> <p>最新记录ID:{{ item.building.building_id }}</p> <p>创建时间:{{ item.building.created_at }}</p> <!-- 正常使用pk生成跳转链接 --> <a href="{% url 'building_detail' item.building.building_id %}">查看详情</a> {% endfor %}
该方法逻辑直观,适合数据量不大的场景。
方法2:数据库聚合+子查询,高效查询
针对数据量较大的场景,用Subquery和OuterRef实现单次数据库查询,避免循环多次查询的性能损耗:
from django.db.models import Subquery, OuterRef, Max # 子查询:获取每个project对应的最新创建时间 max_created_subquery = Building.objects.filter( project=OuterRef('project') ).values('project').annotate(max_created=Max('created_at')).values('max_created') # 主查询:筛选出每个project中创建时间等于对应最大值的记录 latest_buildings = Building.objects.annotate( max_created=Subquery(max_created_subquery) ).filter(created_at=models.F('max_created')).distinct('project') context['latest_buildings'] = latest_buildings
模板中直接遍历QuerySet即可:
{% for building in latest_buildings %} <p>项目:{{ building.project }}</p> <p>最新记录ID:{{ building.building_id }}</p> <p>创建时间:{{ building.created_at }}</p> <a href="{% url 'building_detail' building.building_id %}">查看详情</a> {% endfor %}
MySQL适配提示:如果使用MySQL,distinct('project')不支持,可改用按id关联的写法:
# MySQL适配写法 max_ids = Building.objects.values('project').annotate(max_id=Max('building_id')).values('max_id') latest_buildings = Building.objects.filter(building_id__in=max_ids)
该方法仅需1-2次数据库查询,性能更优。
内容的提问来源于stack exchange,提问作者Murcielago
相关产品推荐
相关产品推荐

