如何在Django中按账户聚合Decimal字段并实现条件求和?
Django实现按ACCOUNT分组聚合并关联原数据的问题
需求说明
按ACCOUNT字段分组,完成以下聚合计算,并将聚合结果关联到每一行原始数据中:
- 对
LIMIT、MARKET、LENDING字段分别求和,别名依次为bond_limit、market_value、lending_value - 对
BALANCE字段做条件求和:大于0的部分求和为FREE,小于0的部分求和为MISS
参考SQL实现
SELECT ACCOUNT, LIMIT, MARKET, LENDING, BALANCE, SUM(LIMIT) OVER (PARTITION BY ACCOUNT) AS bond_limit, SUM(MARKET) OVER (PARTITION BY ACCOUNT) AS market_value, SUM(LENDING) OVER (PARTITION BY ACCOUNT) AS lending_value, SUM(CASE WHEN BALANCE > 0 THEN BALANCE ELSE 0 END) OVER (PARTITION BY ACCOUNT) AS FREE, SUM(CASE WHEN BALANCE < 0 THEN BALANCE ELSE 0 END) OVER (PARTITION BY ACCOUNT) AS MISS FROM your_table;
原始数据与预期结果
原始数据
| ACCOUNT | LIMIT | MARKET | LENDING | BALANCE |
|---|---|---|---|---|
| 1000010 | 200.00 | 0.00 | -234.55 | 1000.00 |
| 1000010 | 300.00 | 11.00 | 0.00 | -239.00 |
| 1000010 | -200.00 | 235.00 | -134.00 | 450.00 |
| 1000011 | 30.00 | 1.00 | -10.00 | -98.00 |
| 1000011 | -200.00 | 235.00 | -134.00 | 49.00 |
预期结果
| ACCOUNT | LIMIT | MARKET | LENDING | BOND_LIMIT | MARKET_VALUE | LENDING_VALUE | FREE | MISS |
|---|---|---|---|---|---|---|---|---|
| 1000010 | 200.00 | 0.00 | -234.55 | 300.00 | 246.00 | -368.55 | 1450.00 | -239.00 |
| 1000010 | 300.00 | 11.00 | 0.00 | 300.00 | 246.00 | -368.55 | 1450.00 | -239.00 |
| 1000010 | -200.00 | 235.00 | -134.00 | 300.00 | 246.00 | -368.55 | 1450.00 | -239.00 |
| 1000011 | 30.00 | 1.00 | -10.00 | -170.00 | 236.00 | -144.00 | 49.00 | -98.00 |
| 1000011 | -200.00 | 235.00 | -134.00 | -170.00 | 236.00 | -144.00 | 49.00 | -98.00 |
遇到的问题
编写的Django代码出现NameError,且不清楚如何实现BALANCE的条件聚合:
# 错误代码1:aggregate返回字典,无法链式调用filter erg = database.objects.filter( account=ACCOUNT ).aggregate( bond_limit=Sum('LIMIT'), market_value=Sum('MARKET'), lending_value=Sum('LENDING') ).filter(bond_limit=bond_limit).aggregate(bond_limit=Sum('LIMIT') ^^^^^^^^^^^ NameError: name 'bond_limit' is not defined
# 错误尝试:不知道如何实现BALANCE的条件求和 erg.filter(BALANCE__gt=0)... # 无思路
解决方案
要实现每行数据关联分组聚合结果,需使用Django的Window函数(对应SQL的窗口函数),配合Case/When实现条件求和。
完整代码示例
from django.db.models import Sum, Case, When, FloatField from django.db.models.expressions import Window from django.db.models.functions import Coalesce # 执行查询,为每行添加分组聚合字段 results = database.objects.annotate( # 按ACCOUNT分组求和LIMIT bond_limit=Window( expression=Sum('LIMIT'), partition_by=['ACCOUNT'] ), # 按ACCOUNT分组求和MARKET market_value=Window( expression=Sum('MARKET'), partition_by=['ACCOUNT'] ), # 按ACCOUNT分组求和LENDING lending_value=Window( expression=Sum('LENDING'), partition_by=['ACCOUNT'] ), # 按ACCOUNT分组,求和大于0的BALANCE FREE=Window( expression=Coalesce(Sum(Case( When(BALANCE__gt=0, then='BALANCE'), default=0, output_field=FloatField() )), 0), partition_by=['ACCOUNT'] ), # 按ACCOUNT分组,求和小于0的BALANCE MISS=Window( expression=Coalesce(Sum(Case( When(BALANCE__lt=0, then='BALANCE'), default=0, output_field=FloatField() )), 0), partition_by=['ACCOUNT'] ) ).values( 'ACCOUNT', 'LIMIT', 'MARKET', 'LENDING', 'bond_limit', 'market_value', 'lending_value', 'FREE', 'MISS' ) # 遍历结果 for item in results: print(item)
关键说明
- Window函数:通过
partition_by=['ACCOUNT']实现按账号分组,将聚合结果关联到每一行,而非合并分组。 - 条件求和:用
Case+When实现类似SQL的case when逻辑,再用Sum包裹完成条件求和;Coalesce用于将可能的NULL结果转为0。 - 避免错误:
aggregate是对整个查询集做单一聚合(返回字典),无法实现分组后关联每行的需求,必须使用annotate+Window组合。
内容的提问来源于stack exchange,提问作者Jay Jay
相关产品推荐
相关产品推荐

