如何通过Django ORM按外键分组聚合用户资产组合数据?
Django ORM实现用户资产组合聚合分组
问题背景
定义了三个Django模型:Coin、User和Portfo,其中Portfo通过外键关联User和Coin,且user与coin组合唯一。需要计算每个用户的资产组合价值,通过ORM按用户分组聚合得到以下两种结构之一:
结构1
[ { "user_id":1, "portfo":{ "coin_1_symbol":Decimal("1"), "coin_2_symbol":Decimal("2"),... } }, ... ]
结构2
[ { "user_id":1, "portfo":[ {"coin_symbol":"some_symbol","total":Decimal("1")}, ... ] },... ]
当前使用的ORM查询返回每条数据对应一个用户的单种代币,未按用户分组整合:
>>> Portfo.objects.exclude(available=0,blocked=0).annotate(total=Sum(F("available")+F("blocked"))).values("user_id","total","coin__symbol") [{"user_id":1,"coin__symbol":"some_symbol","total":Decimal("1")},{"user_id":1,"coin__symbol":"some_symbol2", "total":Decimal("2")},...]
解决方案
实现结构2(ORM原生聚合,适配PostgreSQL)
利用Django的JSONAgg和JSONObject聚合函数,直接在数据库层面完成分组和数组聚合,适合PostgreSQL数据库:
from django.db.models import F, Sum, JSONAgg from django.db.models.functions import JSONObject # 执行查询并返回结构2格式 result = ( Portfo.objects .exclude(available=0, blocked=0) .annotate(total=Sum(F("available") + F("blocked"))) .values("user_id") .annotate( portfo=JSONAgg( JSONObject( coin_symbol=F("coin__symbol"), total=F("total") ) ) ) .values("user_id", "portfo") ) # 转换为列表查看结果 structure2_data = list(result)
实现结构2(兼容所有数据库,Python手动分组)
如果使用MySQL等不支持JSONAgg的数据库,可以先获取扁平数据,再用Python手动分组:
from collections import defaultdict from django.db.models import F, Sum # 获取扁平的聚合数据 queryset = ( Portfo.objects .exclude(available=0, blocked=0) .annotate(total=Sum(F("available") + F("blocked"))) .values("user_id", "coin__symbol", "total") ) # 手动按user_id分组构建结构2 user_portfolios = defaultdict(list) for entry in queryset: user_portfolios[entry["user_id"]].append({ "coin_symbol": entry["coin__symbol"], "total": entry["total"] }) structure2_data = [{"user_id": uid, "portfo": pf} for uid, pf in user_portfolios.items()]
实现结构1
由于ORM无法直接生成动态key的字典结构,需要基于结构2的数据进行转换:
# 基于结构2的数据转换为结构1 structure1_data = [] for item in structure2_data: portfo_dict = {entry["coin_symbol"]: entry["total"] for entry in item["portfo"]} structure1_data.append({ "user_id": item["user_id"], "portfo": portfo_dict })
内容的提问来源于stack exchange,提问作者salar 403
相关产品推荐
相关产品推荐

