Django按月统计:如何合并开启与关闭记录的单查询
合并Django按月统计开启/关闭记录的查询
修正原代码的错误
首先注意你原查询中的一个问题:Q(status__is_open=False) and Q(close_date__isnull=False) 里的 and 会把Q对象转为布尔值,导致过滤逻辑失效,应该用Django Q对象的逻辑与运算符 &:
Q(status__is_open=False) & Q(close_date__isnull=False)
合并为单查询的实现
可以通过一次查询分组统计,同时计算每个月份的开启和关闭记录数,核心是用Count配合filter参数分别统计两类数据,再按年月分组(避免跨年月份混淆):
from django.db.models import Count, Q, F from django.db.models.functions import ExtractMonth, ExtractYear from datetime import datetime, timedelta six_months_ago = datetime.now() - timedelta(days=180) # 合并后的查询:按年月分组,同时统计开启和关闭数量 monthly_stats = ( Rec.objects # 过滤过去六个月内有开启或关闭记录的数据 .filter( Q(open_date__gte=six_months_ago) | Q(close_date__gte=six_months_ago, close_date__isnull=False) ) # 提取开启记录的年月作为分组依据 .annotate( year=ExtractYear(F('open_date')), month=ExtractMonth(F('open_date')) ) # 合并关闭记录的年月分组,确保所有有数据的月份都被覆盖 .union( Rec.objects .filter(close_date__gte=six_months_ago, close_date__isnull=False) .annotate( year=ExtractYear(F('close_date')), month=ExtractMonth(F('close_date')) ) .values('year', 'month') ) # 按年月分组,统计两类记录数 .values('year', 'month') .annotate( # 统计当月开启的记录数 opened=Count( 'id', filter=Q( status__is_open=True, open_date__year=F('year'), open_date__month=F('month'), open_date__gte=six_months_ago ) ), # 统计当月关闭的记录数 closed=Count( 'id', filter=Q( status__is_open=False, close_date__isnull=False, close_date__year=F('year'), close_date__month=F('month'), close_date__gte=six_months_ago ) ) ) # 按年月排序 .order_by('year', 'month') ) # 转换为目标格式(月份缩写+计数) final_data = [] for stat in monthly_stats: month_name = datetime(stat['year'], stat['month'], 1).strftime('%b') final_data.append({ 'month': month_name, 'opened': stat['opened'], 'closed': stat['closed'] })
补充:显示过去所有六个月(含无数据月份)
如果需要强制显示过去六个月的所有月份(即使该月没有开启/关闭记录,计数为0),可以先生成过去六个月的年月列表,再匹配统计数据:
# 生成过去六个月的年月列表 current_date = datetime.now() past_six_months = [] for i in range(6): month_date = current_date - timedelta(days=30*i) past_six_months.append((month_date.year, month_date.month)) # 去重并按年月倒序 past_six_months = sorted(list(set(past_six_months)), reverse=True) # 匹配统计数据,补全无数据月份的计数 final_data = [] for year, month in past_six_months: month_name = datetime(year, month, 1).strftime('%b') # 查找对应年月的统计 match = next((s for s in monthly_stats if s['year'] == year and s['month'] == month), None) final_data.append({ 'month': month_name, 'opened': match['opened'] if match else 0, 'closed': match['closed'] if match else 0 })
输出示例
最终final_data的结构可直接转换为你需要的CSV格式:
Month, opened, closed Jan, 2, 3 Feb, 1, 5 Mar, 0, 2 ...
内容的提问来源于stack exchange,提问作者Bring Coffee Bring Beer
相关产品推荐
相关产品推荐

