Python如何分组年月列表去重年份 格式化Django年月查询结果
问题背景
我有一张存储订单的数据表,表中包含date日期字段,需要从该日期字段聚合提取年份、月份数据,用于前端筛选器使用。目前已经成功查询到对应数据,但返回格式不符合预期。
现有查询代码(Python/Django ORM)
years = purchase_orders.objects.filter(user_id=info.context.user.id).annotate( year=ExtractYear('date'), month=ExtractMonth('date'), ).order_by().values_list('year', 'month').order_by('year', 'month').distinct()
当前返回数据
<QuerySet [(2020, 3), (2021, 4), (2022, 1), (2022, 2), (2022, 3), (2022, 4), (2022, 5)]>
期望输出格式
<QuerySet [(2020, (3)), (2021, (4)), (2022, (1, 2, 3, 4, 5))]>
核心需求:将查询返回的(年份,月份)元组列表按年份分组,去除重复年份条目,将同一年份下的所有月份聚合为嵌套元组,得到符合期望格式的结果。
解决方案
Django原生QuerySet不直接支持元素为嵌套元组的返回结构,你可以根据数据量大小选择以下两种实现方式:
方案1:内存分组(推荐,绝大多数场景适用)
你当前的查询已经完成了年、月维度的去重和排序,直接用Python标准库的itertools.groupby做分组即可,没有额外数据库开销,代码简洁:
from itertools import groupby # 保留原有查询逻辑,拿到排序去重后的年月数据 raw_ym_data = purchase_orders.objects.filter( user_id=info.context.user.id ).annotate( year=ExtractYear('date'), month=ExtractMonth('date'), ).order_by().values_list('year', 'month').order_by('year', 'month').distinct() # 按年份分组,组装目标结构 formatted_result = [] for year, month_group in groupby(raw_ym_data, key=lambda x: x[0]): # 提取同一年份下的所有月份,转为元组 month_tuple = tuple(item[1] for item in month_group) formatted_result.append((year, month_tuple))
执行后formatted_result的结构为:
[(2020, (3,)), (2021, (4,)), (2022, (1, 2, 3, 4, 5))]
注意:Python语法中单个元素的元组必须保留尾部逗号,即
(3,),示例中写的(3)会被识别为整数而非元组,前端对接时注意区分类型。
如果你不需要严格的QuerySet类型,直接使用这个列表即可,完全满足前端筛选器的数据格式要求。
方案2:数据库层聚合(适合百万级以上大表场景)
如果订单表数据量极大,不想把所有年月明细拉到内存处理,可以用数据库的数组聚合函数直接在查询层完成分组:
以PostgreSQL数据库为例,使用Django内置的ArrayAgg聚合器:
from django.contrib.postgres.aggregates import ArrayAgg raw_grouped_data = purchase_orders.objects.filter( user_id=info.context.user.id ).annotate( year=ExtractYear('date') ).values('year').annotate( # 按年分组,聚合对应月份,自动去重、按月份排序 month_list=ArrayAgg( ExtractMonth('date'), distinct=True, ordering='month_list' ) ).order_by('year').values_list('year', 'month_list') # 将数据库返回的月份列表转为元组,匹配目标格式 formatted_result = [(year, tuple(months)) for year, months in raw_grouped_data]
如果使用MySQL数据库,可以替换ArrayAgg为GROUP_CONCAT聚合函数,拿到逗号分隔的月份字符串后,切割转换为整数元组即可。
内容的提问来源于stack exchange,提问作者Ross
相关产品推荐
相关产品推荐

