Django 3中如何搭配_in过滤器使用OuterRef实现关联聚合注解
问题场景
- 业务中存在两个核心模型,分别为存储产品库存的
Product、存储订单项购买信息的OrderLine,模型定义如下:
class Product(models.Model): inventory_quantity = models.IntegerField() class OrderLine(models.Model): product_id = models.ForeignKey(Product, on_delete=models.CASCADE) quantity = models.IntegerField()
- 需求为给
Product查询集添加注解,计算每个产品的预测库存值(现有库存减去所有关联订单项的购买数量总和),原有Django 2版本可用的ORM写法升级到Django 3后存在兼容问题无法运行。
兼容Django 3+的实现方案
Django 3版本调整了子查询中字段引用的校验逻辑,不再支持在子查询内反向引用外层关联模型的字段做计算,直接在外层查询完成库存扣减计算即可,写法更简洁且查询效率更高。
实现关联订单项总数量注解
先通过子查询聚合每个产品关联的所有订单项的购买数量总和,注意给无关联订单项的产品设置默认值0,避免计算结果为空:
from django.db.models import Sum, OuterRef, Subquery, IntegerField, F # 聚合单产品关联订单项总购买量 order_total_subquery = OrderLine.objects.filter( product_id=OuterRef('pk') ).values('product_id') .annotate(total_qty=Sum('quantity')) .values('total_qty')[:1]
计算预测库存注解
将子查询的聚合结果挂载到Product查询集上,直接通过现有库存字段减去总销量得到预测库存:
products_queryset = Product.objects.annotate( # 注解关联订单项总购买量,无订单项时默认取0 total_order_quantity=Subquery( order_total_subquery, output_field=IntegerField(), default=0 ), # 计算预测库存 nb_projected=F('inventory_quantity') - F('total_order_quantity') )
如果不需要单独保留总购买量的注解字段,也可以将逻辑合并简化为单注解写法:
products_queryset = Product.objects.annotate( nb_projected=F('inventory_quantity') - Subquery( OrderLine.objects.filter(product_id=OuterRef('pk')) .values('product_id') .annotate(total_qty=Sum('quantity')) .values('total_qty')[:1], output_field=IntegerField(), default=0 ) )
写法说明
- 子查询末尾加
[:1]是为了确保子查询只返回单个聚合值,符合Subquery表达式的入参要求 default=0参数会自动处理没有任何关联订单项的产品场景,避免子查询返回NULL导致库存计算结果为空- 该写法生成的SQL仅对订单项表做一次聚合查询,不需要额外关联产品表,查询性能比Django 2版本的旧写法更优
内容的提问来源于stack exchange,提问作者Rfayolle
相关产品推荐
相关产品推荐

