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

Django预取查询集中Aggregate无法正常工作的解决办法

解决Django Prefetch中使用aggregate导致的AttributeError问题

错误原因

你遇到的 'dict' object has no attribute '_add_hints' 错误,根源在于:
aggregate() 方法执行后返回的是包含聚合结果的字典,而 Prefetch 要求传入的 queryset 参数必须是 Django 的 QuerySet 对象。当Django尝试对字典调用QuerySet专属的 _add_hints 方法时,自然触发属性错误。


解决方案

方案一:用Subquery+OuterRef在主查询中注解聚合值

这种方式可以将前期借贷总额直接作为字段注解到账户对象上,完全整合到主查询流程里,符合你减少查询次数的需求。

步骤1:导入必要的模块

from django.db.models import Sum, OuterRef, Subquery

步骤2:修改查询逻辑

# 定义子查询,计算每个账户的前期借贷总额
prior_balance_subquery = GeneralLedger.objects.filter(
    # 关联当前外层查询的账户ID
    associated_account_from_chart_of_accounts=OuterRef('pk'),
    # 保留原有的过滤条件
    Q(
        accounts_payable_line_item__property__pk__in=property_pks,
        journal_line_item__property__pk__in=property_pks,
        _connector=Q.OR,
    ),
    date_entered__date__lte=start_date
).values('associated_account_from_chart_of_accounts').annotate(
    prior_credit=Sum('credit_amount'),
    prior_debit=Sum('debit_amount')
).values('prior_credit', 'prior_debit')

# 获取custom_report,同时注解前期余额
custom_report = AccountTree.objects.select_related().prefetch_related(
    'account_tree_total', 'account_tree_regular',
    Prefetch('account_tree_header', queryset=AccountTreeHeader.objects.select_related(
        'associated_account_from_chart_of_accounts', 'associated_total_account_tree_account__associated_account_from_chart_of_accounts'
    ).prefetch_related(
        'associated_regular_account_tree_accounts',
        # 保留原有的range_gl预取逻辑
        Prefetch('associated_regular_account_tree_accounts__associated_account_from_chart_of_accounts__general_ledger',
                 queryset=GeneralLedger.objects.select_related(
                 ).filter(Q(
                     accounts_payable_line_item__property__pk__in=property_pks,
                     journal_line_item__property__pk__in=property_pks,
                     _connector=Q.OR,
                 ), date_entered__date__gte=start_date, date_entered__date__lte=end_date).order_by('date_entered'), to_attr='range_gl'),
    ).annotate(
        # 给账户添加前期余额注解字段
        prior_credit_amount=Subquery(prior_balance_subquery.values('prior_credit')[:1]),
        prior_debit_amount=Subquery(prior_balance_subquery.values('prior_debit')[:1])
    )),
).get(pk=custom_report.pk)

步骤3:修改取值逻辑

直接从账户对象上获取注解的字段即可:

header_accounts = custom_report.account_tree_header.all()

for header_account in header_accounts:
    for regular_account in header_account.associated_regular_account_tree_accounts.all():
        gl_account = regular_account.associated_account_from_chart_of_accounts
        gl_entries = gl_account.range_gl
        # 直接使用注解的字段,默认0避免None值问题
        prior_credit = gl_account.prior_credit_amount or 0
        prior_debit = gl_account.prior_debit_amount or 0

方案二:预计算聚合值存入字典,Python层面匹配

如果觉得Subquery逻辑复杂,可以先单独计算所有相关账户的前期余额,存入字典后在遍历取值,这种方式逻辑更直观。

步骤1:预计算前期余额字典

# 先获取所有需要计算的账户ID
account_ids = AccountTreeHeader.objects.filter(
    account_tree=custom_report
).prefetch_related('associated_regular_account_tree_accounts__associated_account_from_chart_of_accounts').values_list(
    'associated_regular_account_tree_accounts__associated_account_from_chart_of_accounts__pk', flat=True
).distinct()

# 计算这些账户的前期余额
prior_balances = GeneralLedger.objects.filter(
    associated_account_from_chart_of_accounts__pk__in=account_ids,
    Q(
        accounts_payable_line_item__property__pk__in=property_pks,
        journal_line_item__property__pk__in=property_pks,
        _connector=Q.OR,
    ),
    date_entered__date__lte=start_date
).values('associated_account_from_chart_of_accounts').annotate(
    prior_credit=Sum('credit_amount'),
    prior_debit=Sum('debit_amount')
)

# 转成字典,key为账户ID,value为借贷数据
prior_balance_dict = {
    item['associated_account_from_chart_of_accounts']: {
        'prior_credit': item['prior_credit'] or 0,
        'prior_debit': item['prior_debit'] or 0
    } for item in prior_balances
}

步骤2:获取custom_report(无需处理old_gl的Prefetch)

custom_report = AccountTree.objects.select_related().prefetch_related(
    'account_tree_total', 'account_tree_regular',
    Prefetch('account_tree_header', queryset=AccountTreeHeader.objects.select_related(
        'associated_account_from_chart_of_accounts', 'associated_total_account_tree_account__associated_account_from_chart_of_accounts'
    ).prefetch_related(
        'associated_regular_account_tree_accounts',
        Prefetch('associated_regular_account_tree_accounts__associated_account_from_chart_of_accounts__general_ledger',
                 queryset=GeneralLedger.objects.select_related(
                 ).filter(Q(
                     accounts_payable_line_item__property__pk__in=property_pks,
                     journal_line_item__property__pk__in=property_pks,
                     _connector=Q.OR,
                 ), date_entered__date__gte=start_date, date_entered__date__lte=end_date).order_by('date_entered'), to_attr='range_gl'),
    )),
).get(pk=custom_report.pk)

步骤3:遍历取值

header_accounts = custom_report.account_tree_header.all()

for header_account in header_accounts:
    for regular_account in header_account.associated_regular_account_tree_accounts.all():
        gl_account = regular_account.associated_account_from_chart_of_accounts
        gl_entries = gl_account.range_gl
        # 从字典中获取对应账户的前期余额
        balance_data = prior_balance_dict.get(gl_account.pk, {'prior_credit': 0, 'prior_debit': 0})
        prior_credit = balance_data['prior_credit']
        prior_debit = balance_data['prior_debit']

总结

不要在 Prefetch 的 queryset 参数中使用 aggregate(),因为它返回的是字典而非QuerySet。推荐优先使用方案一,通过Subquery将聚合逻辑整合到主查询中,最大程度减少数据库查询次数;如果对Subquery不熟悉,方案二的预计算字典方式也能解决问题,逻辑更易理解。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 19:05:00