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

Django annotate多Sum字段导致结果重复的问题排查

解决Django多关联表Annotate求和重复问题

问题场景

单独对TransactionOut字段求和时结果正确,但同时添加TransactionIn的求和注解后,两个字段的结果均出现倍数重复(如2000变为4000)。

单注解正确代码

centers = Center.objects.filter(branch=request.user.center.branch,name="test").annotate(
    transaction_out=Coalesce(Sum('_TransactionOut__amount', filter=Q(_TransactionOut__date_time__gte=thirty_days_ago)),Value(0.0)),
)

添加双注解后出现重复的代码

centers = Center.objects.filter(branch=request.user.center.branch,name="test").annotate(
    transaction_in=Coalesce(Sum('_TransactionIn__amount', filter=Q(_TransactionIn__date_time__gte=thirty_days_ago)),Value(0.0)),
    transaction_out=Coalesce(Sum('_TransactionOut__amount', filter=Q(_TransactionOut__date_time__gte=thirty_days_ago)),Value(0.0)),
)

模型定义

class TransactionIn(models.Model):
    amount = models.FloatField()
    center=models.ForeignKey("center.Center",on_delete=models.SET_NULL,null=True,blank=True, related_name="_TransactionIn")

class TransactionOut(models.Model):
    amount = models.FloatField()
    user=models.ForeignKey("account.User",on_delete=models.SET_NULL,null=True,blank=True)
    center=models.ForeignKey("center.Center",on_delete=models.SET_NULL,null=True,blank=True, related_name="_TransactionOut")

class Center(models.Model):
    name = models.CharField(max_length=35)

生成的SQL查询语句

SELECT "center_center"."id",
       "center_center"."name",
       "center_center"."branch_id",
       "center_center"."image",
       "center_center"."manager_id",
       "center_center"."lat",
       "center_center"."long",
       "center_center"."mobile_number",
       COALESCE(SUM("accounting_transactionin"."amount") FILTER (WHERE "accounting_transactionin"."date_time" >= '2023-06-10T21:08:09.278740+00:00'::timestamptz), 0.0) AS "transaction_in",
       COALESCE(SUM("accounting_transactionout"."amount") FILTER (WHERE "accounting_transactionout"."date_time" >= '2023-06-10T21:08:09.278740+00:00'::timestamptz), 0.0) AS "transaction_out"
  FROM "center_center"
  LEFT OUTER JOIN "accounting_transactionin"
    ON ("center_center"."id" = "accounting_transactionin"."center_id")
  LEFT OUTER JOIN "accounting_transactionout"
    ON ("center_center"."id" = "accounting_transactionout"."center_id")
 WHERE ("center_center"."branch_id" = 1 AND "center_center"."name" = 'test')
 GROUP BY "center_center"."id"

问题原因

两次LEFT JOIN操作生成了笛卡尔积:假设一个Center关联2条TransactionIn和2条TransactionOut,JOIN后会产生2×2=4条记录,求和时每条TransactionIn的amount会被计算2次,TransactionOut的amount也会被计算2次,最终结果出现倍数重复。

解决方案

方法1:使用子查询(Subquery)分别计算求和值

通过子查询单独计算每个关联表的求和结果,避免JOIN带来的笛卡尔积问题,兼容性覆盖所有数据库:

from django.db.models import Subquery, OuterRef

# 子查询计算transaction_in
transaction_in_subquery = TransactionIn.objects.filter(
    center=OuterRef('pk'),
    date_time__gte=thirty_days_ago
).values('center').annotate(
    total=Sum('amount')
).values('total')

# 子查询计算transaction_out
transaction_out_subquery = TransactionOut.objects.filter(
    center=OuterRef('pk'),
    date_time__gte=thirty_days_ago
).values('center').annotate(
    total=Sum('amount')
).values('total')

# 主查询
centers = Center.objects.filter(
    branch=request.user.center.branch,
    name="test"
).annotate(
    transaction_in=Coalesce(Subquery(transaction_in_subquery), Value(0.0)),
    transaction_out=Coalesce(Subquery(transaction_out_subquery), Value(0.0))
)

方法2:使用distinct参数配合Sum(仅PostgreSQL可用)

如果使用PostgreSQL数据库,可以在Sum中添加distinct=True,基于关联表的主键去重,确保每条记录只被计算一次:

centers = Center.objects.filter(branch=request.user.center.branch,name="test").annotate(
    transaction_in=Coalesce(Sum('_TransactionIn__amount', filter=Q(_TransactionIn__date_time__gte=thirty_days_ago), distinct=True), Value(0.0)),
    transaction_out=Coalesce(Sum('_TransactionOut__amount', filter=Q(_TransactionOut__date_time__gte=thirty_days_ago), distinct=True), Value(0.0)),
)

方法3:通过Count修正重复结果(临时方案,不推荐)

如果已知重复倍数,可以用Count获取关联记录数后修正结果,但该方法灵活性差,仅适用于临时场景:

from django.db.models import Case, When, FloatField

centers = Center.objects.filter(branch=request.user.center.branch,name="test").annotate(
    transaction_in_sum=Coalesce(Sum('_TransactionIn__amount', filter=Q(_TransactionIn__date_time__gte=thirty_days_ago)),Value(0.0)),
    transaction_out_sum=Coalesce(Sum('_TransactionOut__amount', filter=Q(_TransactionOut__date_time__gte=thirty_days_ago)),Value(0.0)),
    in_count=Count('_TransactionIn', filter=Q(_TransactionIn__date_time__gte=thirty_days_ago), distinct=True),
    out_count=Count('_TransactionOut', filter=Q(_TransactionOut__date_time__gte=thirty_days_ago), distinct=True)
).annotate(
    transaction_in=Case(
        When(in_count__gt=0, then=F('transaction_in_sum') / F('out_count')),
        default=Value(0.0),
        output_field=FloatField()
    ),
    transaction_out=Case(
        When(out_count__gt=0, then=F('transaction_out_sum') / F('in_count')),
        default=Value(0.0),
        output_field=FloatField()
    )
)

推荐方案

优先选择方法1(子查询),逻辑清晰且兼容性强;若使用PostgreSQL环境,**方法2(distinct参数)**代码更简洁,也是优质选择。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 05:03:11