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

优化Django查询:减少循环并实现历史记录前置状态判定

优化思路与解决方案

核心逻辑拆解

原代码的目标是:筛选**DataActionItem的历史记录中状态为'resolved'、时间在最近7天,且该历史记录的上一条记录状态不为'resolved'**的条目,最终统计这些条目对应日期的出现次数。你当前的ORM查询卡在了前置记录状态的校验环节,这里可以用Django的窗口函数直接在数据库层面完成过滤,彻底消除Python循环的开销。

完整优化代码

from django.db.models import Count, F
from django.db.models.functions import TruncDate, Lag
from django.utils import timezone

# 定义时间范围(用timezone处理时区更严谨)
today = timezone.now().date()
seven_days_ago = today - timezone.timedelta(days=7)

# 单次数据库查询完成所有逻辑
result = (
    DataActionItem.history
    .filter(
        site_id=settings.SITE_ID,
        status='resolved',
        history_date__date__range=(seven_days_ago, today)
    )
    # 按原DataActionItem分组,按历史时间排序,获取前一条记录的状态
    .annotate(
        prev_status=Lag('status', partition_by=F('object_id'), order_by=F('history_date'))
    )
    # 过滤前一条状态不是resolved的记录(第一条记录prev_status为None,也符合条件)
    .filter(prev_status__ne='resolved')
    # 按日期截断并统计每日数量
    .annotate(day=TruncDate('history_date'))
    .values('day')
    .annotate(count=Count('id'))
    .order_by('day')
)

关键逻辑说明

  • Lag('status', partition_by=F('object_id'), order_by=F('history_date')):通过窗口函数Lag,按每个原DataActionItem(object_id对应原item的ID)分组,按历史记录的时间顺序排序,自动获取每条记录的前一条记录的status。
  • filter(prev_status__ne='resolved'):直接筛选出符合要求的记录——前一条状态不是'resolved',包含每个item的第一条resolved记录(此时prev_status为None,也满足过滤条件)。
  • 后续的TruncDate和Count操作,直接完成原代码中取最晚日期、统计每日次数的逻辑,所有计算都在数据库层面完成,避免了Python循环和N+1查询的性能问题。

性能优势

  1. 原代码存在N+1查询问题:先查所有resolved的item,再循环每个item查历史记录;优化后的查询是单次数据库请求,性能大幅提升。
  2. 消除了Python与数据库之间的大量数据传输开销,所有过滤、统计逻辑都在数据库端执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 01:40:46