Django中使用多Sum函数执行annotate时计算结果错误的问题
我明白你遇到的问题了——当你用annotate()同时对两个反向关联的字段做Sum()聚合时,Django会生成跨多张表的JOIN查询,这会导致笛卡尔积问题,最终让求和结果被重复计算,出现错误数值。
先看你当前生成的SQL,wallet_donation的子查询居然按U0.id分组,这明显不对,应该按condition_id分组才能得到单个Condition的所有钱包交易总和;而且即使分组正确,直接用annotate(Sum(...))做多表JOIN依然会触发笛卡尔积,导致结果失真。
下面是用Subquery解决这个问题的具体方案,我会直接基于你的代码修改:
第一步:导入所需的ORM工具
在managers.py顶部补充导入:
from django.db.models import Subquery, OuterRef, Coalesce, Sum, F
第二步:修改with_donations_stats方法
把原来的直接annotate(Sum(...))替换为子查询聚合:
def with_donations_stats(self): # 构造钱包交易总和的子查询:针对当前Condition,计算所有关联WalletTransaction的金额总和 wallet_sum = WalletTransaction.objects.filter( condition=OuterRef('pk') ).values('condition').annotate( total=Sum('amount') ).values('total') # 只提取总和值,确保子查询返回单个结果 # 构造普通交易总和的子查询:逻辑同上 normal_sum = Transaction.objects.filter( condition=OuterRef('pk') ).values('condition').annotate( total=Sum('amount') ).values('total') return self.annotate( wallet_donation=Coalesce(Subquery(wallet_sum), 0), normal_donation=Coalesce(Subquery(normal_sum), 0), total_donation=F('wallet_donation') + F('normal_donation') )
为什么这个方案能解决问题?
原来的annotate(Sum(...))会让Django把Condition和两个关联表做JOIN:如果一个Condition有n个钱包交易和m个普通交易,JOIN后的结果会有n*m条记录,这就导致Sum()计算时把每个交易的金额重复加了多次。
而用Subquery的方式,每个求和都是单独针对当前Condition的主键去子查询关联表的总和,完全避免了多表JOIN,自然就不会产生笛卡尔积,求和结果也就准确了。
修改后的SQL逻辑验证
修改后生成的wallet_donation子查询会变成类似这样:
COALESCE((SELECT SUM(U0."amount") FROM "wallets_wallettransaction" U0 WHERE U0."condition_id" = ("conditions"."id") GROUP BY U0."condition_id"), 0) AS "wallet_donation"
这个子查询会正确返回单个Condition的所有钱包交易总和,没有重复计算的问题。
你可以把这个修改应用到代码中,然后测试completed()和not_completed()方法的结果,应该就能得到正确的聚合值了。
内容的提问来源于stack exchange,提问作者Ahmed Samy

