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
相关产品推荐
相关产品推荐

