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

如何通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 00:35:29