Django查询父模型时如何注解子模型关联字段聚合值
Django多表继承下跨子模型条件聚合方案
问题明确答复
- 完全可以通过父模型查询注解子模型的关联数据。你使用的是Django默认的多表继承模式,父类
Order和每个子类之间会自动创建一对一关联,关联名默认为子类名的全小写形式:Order到BorrowOrder的关联为borroworder,到ReturnOrder的关联为returnorder。查询父类时ORM会自动通过LEFT JOIN关联子类表,支持通过双下划线语法跨子类、子类的关联表取字段。 - 可以通过
Case+When实现条件选择聚合字段,注意处理NULL值即可。
可直接使用的实现代码
基础写法(适合子类数量少、数据量不大的场景)
from django.db.models import Sum, Case, When, Value, Coalesce qs = Order.objects.annotate( total_qty=Sum( Case( # 匹配借阅单类型,取借阅中间表的数量字段 When( borroworder__isnull=False, then=Coalesce('borroworder__borrowed_parts__qty_borrowed', Value(0)) ), # 匹配归还单类型,取归还中间表的数量字段 When( returnorder__isnull=False, then=Coalesce('returnorder__returned_parts__qty_returned', Value(0)) ), # 其他类型订单默认数量为0 default=Value(0) ) ) )
写法说明
- 用
borroworder__isnull=False判断当前订单属于BorrowOrder子类,这是Django多表继承下判断子类类型的标准ORM写法,无需额外加字段。 Coalesce的作用是将关联不存在时产生的NULL值转为0,避免Sum聚合时因NULL值导致结果异常。- 字段路径遵循ORM双下划线规则:父模型 -> 子类一对一关联 -> 中间表反向关联(即你在中间表外键定义的
related_name) -> 数量字段。 - 后续新增订单子类时,只需要在
Case中追加对应的When分支即可。
高性能写法(适合数据量大、子类多的场景)
基础写法会通过LEFT JOIN关联所有子类表、子类对应的中间表,数据量大时可能有性能损耗,可改用子查询聚合避免多表连接:
from django.db.models import Sum, Case, When, Value, Coalesce, Subquery, OuterRef # 构建借阅单数量聚合子查询 borrow_total = BorrowOrderPart.objects.filter( borrow_order_id=OuterRef('pk') ).values('borrow_order_id').annotate( total=Sum('qty_borrowed') ).values('total')[:1] # 构建归还单数量聚合子查询 return_total = ReturnOrderPart.objects.filter( return_order_id=OuterRef('pk') ).values('return_order_id').annotate( total=Sum('qty_returned') ).values('total')[:1] qs = Order.objects.annotate( total_qty=Coalesce( Case( When(borroworder__isnull=False, then=Subquery(borrow_total)), When(returnorder__isnull=False, then=Subquery(return_total)), default=Value(0) ), Value(0) ) )
这种写法每个子查询单独聚合对应中间表的数量,不会产生多表连接的重复行问题,数据量越大性能优势越明显。
内容的提问来源于stack exchange,提问作者Jonah Bishop
相关产品推荐
相关产品推荐

