如何用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
相关产品推荐
相关产品推荐

