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

Django实现JsonField数组值按wh与created_at分组求和

Django JsonField嵌套数组分组聚合实现

方案1:PostgreSQL 原生SQL实现(大数据量首选)

嵌套JSON数组的聚合无法直接通过基础ORM的annotate完成,核心逻辑是先通过数据库JSON函数把两层嵌套数组逐行拆平,再按日期和wh编号分组求和。PostgreSQL作为Django JsonField原生适配的数据库,用jsonb_array_elements拆数组性能最高。

对应SQL逻辑:

WITH flattened_stocks AS (
    SELECT
        DATE(sh.created_at) AS record_date,
        (stocks_item ->> 'wh')::INTEGER AS wh_id,
        (stocks_item ->> 'qty')::INTEGER AS qty
    FROM stocks_history sh
    -- 展开第一层wh_data数组
    CROSS JOIN jsonb_array_elements(sh.wh_data) AS wh_data_item
    -- 展开第二层每个条目下的stocks数组
    CROSS JOIN jsonb_array_elements(wh_data_item -> 'stocks') AS stocks_item
)
SELECT
    wh_id AS wh,
    SUM(qty) AS qty,
    record_date AS created_at
FROM flattened_stocks
GROUP BY record_date, wh_id
ORDER BY record_date, wh_id;

在Django中直接用raw方法调用即可:

# 执行原生查询
raw_results = StocksHistory.objects.raw(
    """
    WITH flattened_stocks AS (
        SELECT
            sh.id,
            DATE(sh.created_at) AS record_date,
            (stocks_item ->> 'wh')::INTEGER AS wh_id,
            (stocks_item ->> 'qty')::INTEGER AS qty
        FROM stocks_history sh
        CROSS JOIN jsonb_array_elements(sh.wh_data) AS wh_data_item
        CROSS JOIN jsonb_array_elements(wh_data_item -> 'stocks') AS stocks_item
    )
    SELECT
        wh_id AS wh,
        SUM(qty) AS qty,
        record_date AS created_at
    FROM flattened_stocks
    GROUP BY record_date, wh_id
    ORDER BY record_date, wh_id
    """
)

# 转换为要求的JSON结构
final_results = [
    {
        "wh": item.wh,
        "qty": item.qty,
        "created_at": item.created_at.isoformat()
    }
    for item in raw_results
]

方案2:Python内存聚合(中小数据量首选,全数据库兼容)

如果单表总记录数在万级以内,完全没必要写数据库绑定的复杂JSON语法,直接查询需要的字段到内存中聚合,代码逻辑简单易维护,不限制数据库类型(MySQL、SQLite都能用)。

from collections import defaultdict

# 只查询需要的字段,减少数据传输量
qs = StocksHistory.objects.values("wh_data", "created_at")
agg_map = defaultdict(int)

for record in qs:
    record_day = record["created_at"].date()
    # 遍历第一层wh_data数组
    for entry in record["wh_data"]:
        # 遍历第二层stocks数组累加qty
        for stock in entry.get("stocks", []):
            agg_key = (record_day, stock["wh"])
            agg_map[agg_key] += stock["qty"]

# 转换为要求的输出格式
final_results = []
for (day, wh_id), total_qty in agg_map.items():
    final_results.append({
        "wh": wh_id,
        "qty": total_qty,
        "created_at": day.isoformat()
    })

补充说明:

  • 如果需要实现示例中某日期下对应wh无库存记录时返回qty=0的效果,只需提前拉取全量wh编号列表、全量有记录的日期列表,遍历两者的组合作为key,聚合结果中不存在的key默认赋值0即可。
  • 如果使用MySQL 8.0+版本,可将方案1中的jsonb_array_elements替换为JSON_TABLE函数实现同等的数组拆包逻辑,低版本MySQL建议直接用方案2。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 20:01:21