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

如何用Django ORM实现按Unit分组统计PurchaseOrder总Quantity并减DB查询?

嘿,我明白你现在的困扰——想要在PurchaseOrder列表里按unit分组展示总quantity,又不想触发N+1查询对吧?我来给你几个可行的Django ORM解决方案~

先分析下你之前的尝试问题:

  • 直接在每个PO对象调用get_quantity_by_unit()会触发N+1查询,因为每个对象都会单独发起一次数据库请求;
  • 你尝试的annotate写法没有实现分组,是因为只对quantity做了求和,没有关联unit字段做分组维度;
  • Prefetch用values()报错是因为Django的Prefetch不允许传入返回字典的queryset,它要求queryset返回模型实例。

解决方案1:预查询分组统计+手动关联(兼容所有Django版本)

这种方法逻辑简单直接,通过两次查询完成需求,完全避免N+1问题:

from django.db.models import Sum

# 1. 先查询目标PO对应的所有line分组统计,只获取需要的字段
line_stats = PurchaseOrderLine.objects.filter(
    purchase_order__date__gte=cutoff_date
).values(
    'purchase_order_id', 'unit__abbreviation'
).annotate(
    total_quantity=Sum('quantity')
)

# 2. 把统计数据整理成「PO ID → unit统计映射」的字典
po_unit_map = {}
for stat in line_stats:
    po_id = stat['purchase_order_id']
    if po_id not in po_unit_map:
        po_unit_map[po_id] = {}
    po_unit_map[po_id][stat['unit__abbreviation']] = stat['total_quantity']

# 3. 查询PO列表,给每个PO对象添加上统计属性
purchase_orders = PurchaseOrder.objects.filter(date__gte=cutoff_date)
for po in purchase_orders:
    # 无统计数据时返回空字典
    po.unit_totals = po_unit_map.get(po.id, {})

之后你在模板里就可以直接遍历po.unit_totals.items(),展示每个unit对应的总quantity了。

解决方案2:用JSONAgg+Subquery(Django 3.1+适用)

如果你的Django版本在3.1及以上,可以用ORM的JSON聚合函数,直接把每个PO的分组统计作为字段返回,写法更优雅:

from django.db.models import Sum, F, Subquery, OuterRef
from django.db.models.functions import JSONObject, JSONAgg

# 子查询:针对单个PO,按unit分组计算总和并包装成JSON对象
unit_stats_subquery = PurchaseOrderLine.objects.filter(
    purchase_order=OuterRef('pk')
).values('unit__abbreviation').annotate(
    total=Sum('quantity')
).annotate(
    unit_total=JSONObject(unit=F('unit__abbreviation'), total=F('total'))
).values('unit_total')

# 主查询:给每个PO添加unit_totals字段,值为包含所有unit统计的JSON数组
purchase_orders = PurchaseOrder.objects.filter(date__gte=cutoff_date).annotate(
    unit_totals=JSONAgg(Subquery(unit_stats_subquery))
)

每个purchase_order对象会生成一个unit_totals属性,格式类似[{"unit": "kg", "total": 150}, {"unit": "pcs", "total": 50}],直接在模板里遍历这个数组就能展示数据,而且只需要一次数据库查询。

两种方案对比:

  • 方案1兼容性强,逻辑清晰易调试,适合所有Django版本;
  • 方案2更贴合ORM风格,代码更简洁,但依赖Django 3.1+的JSON函数支持。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 10:04:06