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

Django ORM实现双关联(Double Join)与Sum聚合查询问题求助

问题:账户分组、下属账户及余额统计查询

我在Stack Overflow(SO)和谷歌上搜索了类似案例,但未找到可行解决方案。

问题简述

交易记录(Transaction)归属账户(Account),账户又归属账户分组(AccountsAggrupation)。我希望获取账户分组列表及其下属账户,同时统计每个账户的总余额(账户余额为该账户所有交易记录的金额之和)。

详细说明

我定义了以下模型(为完整展示,包含相关Mixin):

class UniqueNameMixin(models.Model):
    class Meta:
        abstract = True
    name = models.CharField(verbose_name=_('name'), max_length=100, unique=True)
    def __str__(self):
        return self.name

class PercentageMixin(UniqueNameMixin):
    class Meta:
        abstract = True
    _validators = [MinValueValidator(0), MaxValueValidator(100)]
    current_percentage = models.DecimalField(max_digits=5, decimal_places=2, validators=_validators, null=True, blank=True)
    ideal_percentage = models.DecimalField(max_digits=5, decimal_places=2, validators=_validators, null=True, blank=True)

class AccountsAggrupation(PercentageMixin):
    pass

class Account(PercentageMixin):
    aggrupation = models.ForeignKey(AccountsAggrupation, models.PROTECT)

class Transaction(models.Model):
    date = models.DateField()
    concept = models.ForeignKey(Concept, models.PROTECT, blank=True, null=True)
    amount = models.DecimalField(max_digits=10, decimal_places=2)
    account = models.ForeignKey(Account, models.PROTECT)
    detail = models.CharField(max_length=100, blank=True, null=True)
    def __str__(self):
        return '{} - {} - {} - {}'.format(self.date, self.concept, self.amount, self.account)

我希望通过Django ORM实现如下SQL查询的效果:

select ca.*, ca2.*, sum(ct.amount) from core_accountsaggrupation ca join core_account ca2 on ca2.aggrupation_id = ca.id join core_transaction ct on ct.account_id = ca2.id group by ca2.name order by ca.name;

解决方案

嘿,这个需求我之前做项目时也碰到过,用Django ORM完全能搞定,不用硬写原生SQL。下面给你两种实用的方法,看哪种更贴合你的业务场景:

方法一:从账户出发,聚合余额后关联分组

这种方式先计算每个账户的总余额,再关联到对应的账户分组,最后可以用Python简单整理成分组结构:

from django.db.models import Sum

# 查询所有账户,带上余额总和,同时关联所属分组
accounts_queryset = Account.objects.annotate(
    total_balance=Sum('transaction__amount')
).select_related('aggrupation').order_by('aggrupation__name', 'name')

# 把结果按分组整理成字典(方便后续遍历使用)
grouped_data = {}
for account in accounts_queryset:
    agg = account.aggrupation
    if agg not in grouped_data:
        grouped_data[agg] = []
    # 处理无交易的账户,余额设为0
    balance = account.total_balance or 0
    grouped_data[agg].append({
        'account': account,
        'total_balance': balance
    })

方法二:从分组出发,预取带余额的账户

如果你想直接从账户分组开始查询,并且一次性获取所有分组下的账户及余额,可以用Prefetch来实现精准预取,效率更高:

from django.db.models import Sum, Prefetch

# 定义预取的账户查询集:每个账户带上余额总和
prefetched_accounts = Prefetch(
    'account_set',  # AccountsAggrupation关联Account的反向关联名
    queryset=Account.objects.annotate(total_balance=Sum('transaction__amount')),
    to_attr='accounts_with_balance'  # 自定义属性名,方便后续调用
)

# 查询所有分组,预取处理好的账户数据,按分组名称排序
aggrupations = AccountsAggrupation.objects.prefetch_related(
    prefetched_accounts
).order_by('name')

# 遍历结果示例
for agg in aggrupations:
    print(f"分组名称:{agg.name}")
    for account in agg.accounts_with_balance:
        # 处理无交易的情况
        total = account.total_balance if account.total_balance is not None else 0
        print(f"  账户:{account.name},总余额:{total}")

为什么这两种方法能匹配你的SQL?

  • 两种方式都实现了join关联(ORM会自动处理外键关联),并且通过Sum('transaction__amount')完成了金额聚合
  • order_by('name')对应你SQL里的order by ca.name,保证分组按名称排序
  • group by的逻辑由ORM的annotate自动处理,针对每个账户聚合交易金额,和你SQL里的group by ca2.name效果一致

另外提醒下:如果有账户没有任何交易记录,Sum会返回None,所以记得用or 0或者条件判断把它转成0,避免后续计算或展示出问题。


内容的提问来源于stack exchange,提问作者Alvaro Rodriguez Scelza

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 18:14:09