Django如何对QuerySet排序同时按type字段分组保持同类型对象相邻
实现方案
不需要编写原生SQL,也不需要硬编码Case When语句,用Django ORM自带的窗口函数就能实现需求,且天然支持分页。
核心逻辑
你的排序需求本质是两个排序优先级:
- 第一优先级:按每条记录所属
type分组的最新日期倒序,最新日期越大的分组整体越靠前 - 第二优先级:同分组内按记录本身的
date字段倒序
推荐写法(支持现代数据库)
如果你的数据库是MySQL 8.0+、PostgreSQL、SQLite 3.25+(绝大多数项目的运行环境都满足),直接用窗口函数注解分组最新日期即可:
from django.db.models import F, Max, Window queryset = Obj.objects.annotate( # 为每条记录附加其所属type的最新日期值,同type记录该值完全一致 type_group_latest_date=Window( expression=Max("date"), partition_by=[F("type")] ) ).order_by("-type_group_latest_date", "-date")
这个写法完全匹配你给出的示例预期:
- GREEN分组最新日期为2022.01.07,是所有分组中最大的,因此排在最前,组内按日期倒序为07.01、04.01
- BLACK分组最新日期为2022.01.06,排在第二组,组内顺序为06.01、02.01
- BLUE分组最新日期为2022.01.05,排在最后,组内顺序为05.01、03.01、01.01
老版本数据库兼容写法
如果你还在使用不支持窗口函数的MySQL 5.x版本,可以用子查询替代窗口函数实现相同逻辑:
from django.db.models import OuterRef, Subquery # 子查询:获取每个type对应的最新日期 type_latest_date_subquery = Obj.objects.filter( type=OuterRef("type") ).order_by("-date").values("date")[:1] queryset = Obj.objects.annotate( type_group_latest_date=Subquery(type_latest_date_subquery) ).order_by("-type_group_latest_date", "-date")
方案说明
- 不推荐用
Case When实现:Case When适合提前固定分组排序权重的场景,你的需求中分组顺序是随数据动态变化的,硬编码Case When无法适配数据更新,维护成本极高 - 上述两种写法返回的都是标准Django QuerySet对象,完全兼容Django自带的
Paginator分页能力,所有排序计算都在数据库层面完成,不需要把全量数据加载到Python内存排序,性能有保障 - 建议给
type、date字段建立联合索引,可以进一步提升查询排序效率 - 你之前用的
order_by("type", "-date")本质是按type字段本身的枚举/字典序排分组,不是按分组的最新时间排序,自然不符合需求
内容的提问来源于stack exchange,提问作者Işık Kaplan
相关产品推荐
相关产品推荐

