You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 12:57:34