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

Django多时间区间字段求和查询优化及DRY实现方案咨询

实现方案

方案1:抽离公共逻辑(改动最小,满足DRY原则)

把重复的查询逻辑、日期计算逻辑分别封装成辅助方法,避免冗余代码:

from django.db.models import F, Sum
import datetime

# 先封装获取n个月前对应日期的方法
def get_n_months_ago(current_date, n):
    year = current_date.year - (current_date.month - n - 1) // 12
    month = (current_date.month - n - 1) % 12 + 1
    return datetime.datetime(year, month, current_date.day)

# 封装时长统计逻辑
def calc_rpic_total_duration(user, start_date, end_date):
    return RPLogbookEntry.objects.filter(
        user=user,
        user_role='RPIC',
        entry_date__range=(start_date, end_date)
    ).annotate(
        ft_total=F('ft_day') + F('ft_night')
    ).aggregate(Sum('ft_total'))

然后在get_context_data里直接调用即可:

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    current_user = self.request.user
    now = self.date_now
    # 直接传参数调用即可,新增时间区间只需要加一行
    context['last_6_months_rpic'] = calc_rpic_total_duration(current_user, get_n_months_ago(now, 6), now)
    context['last_12_months_rpic'] = calc_rpic_total_duration(current_user, get_n_months_ago(now, 12), now)
    # 比如新增最近7天统计,加一行就行
    # context['last_7d_rpic'] = calc_rpic_total_duration(current_user, now - datetime.timedelta(days=7), now)
    return context

方案2:单次查询统计所有区间(查询效率更高)

如果需要统计的时间区间较多,可只查一次全量符合条件的记录,在内存中完成多区间统计,仅需1次数据库查询:

def get_context_data(self, **kwargs):
    context = super().get_context_data(**kwargs)
    current_user = self.request.user
    now = self.date_now
    # 先查最大时间范围(这里是12个月)的所有符合条件的记录,只查需要的字段
    entries = RPLogbookEntry.objects.filter(
        user=current_user,
        user_role='RPIC',
        entry_date__range=(get_n_months_ago(now, 12), now)
    ).values('entry_date', 'ft_day', 'ft_night')
    
    # 预计算各个时间区间的起始时间
    start_6m = get_n_months_ago(now, 6)
    start_12m = get_n_months_ago(now, 12)
    start_7d = now - datetime.timedelta(days=7)
    
    # 遍历一次记录,统计所有区间的总和
    last_6m_total = last_12m_total = last_7d_total = 0
    for entry in entries:
        ft_total = entry['ft_day'] + entry['ft_night']
        entry_date = entry['entry_date']
        if entry_date >= start_12m:
            last_12m_total += ft_total
        if entry_date >= start_6m:
            last_6m_total += ft_total
        if entry_date >= start_7d:
            last_7d_total += ft_total
    
    # 赋值到context
    context['last_6_months_rpic'] = {'ft_total__sum': last_6m_total}
    context['last_12_months_rpic'] = {'ft_total__sum': last_12m_total}
    context['last_7d_rpic'] = {'ft_total__sum': last_7d_total}
    return context

额外优化提示

如果ft_total的计算逻辑需要多处使用,可以把F('ft_day') + F('ft_night')封装成单独的表达式变量,避免重复编写:

from django.db.models import ExpressionWrapper, IntegerField

FT_TOTAL_EXPR = ExpressionWrapper(F('ft_day') + F('ft_night'), output_field=IntegerField())

后续需要用到的地方直接替换成FT_TOTAL_EXPR即可。

内容的提问来源于stack exchange,提问作者nick

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 05:36:02