Django:使用extra()替代annotate()为产品标注近7天销售总重量
解决方案:用extra()为Product标注过去7天销售总重量
嘿,这问题我熟!之前也碰到过多次关联下annotate()重复计数的坑,用extra()通过子查询来实现确实能完美避开这个问题,我来给你一步步拆解:
先明确必要的模型补充
你给出的Order模型没写完,我先补上实现需求必须的字段(如果你的实际字段名不同,对应调整就行):
class Order(models.Model): template = models.ForeignKey(Template, on_delete=models.CASCADE) # 和Template关联 weight = models.DecimalField(max_digits=10, decimal_places=2) # 订单重量 created_at = models.DateTimeField(auto_now_add=True) # 订单创建时间
核心查询代码
下面就是用extra()实现的查询逻辑,直接给每个Product实例加上total_sales_weight属性:
from django.utils import timezone from datetime import timedelta # 计算7天前的时间点 seven_days_ago = timezone.now() - timedelta(days=7) # 用extra()子查询获取每个Product的销售总重量 products_with_sales = Product.objects.extra( select={ 'total_sales_weight': """ SELECT SUM(o.weight) FROM your_app_order o JOIN your_app_template t ON o.template_id = t.id WHERE t.product_id = your_app_product.id AND o.created_at >= %s """ }, select_params=(seven_days_ago,), # 传递参数避免SQL注入 )
代码细节说明
- 为什么选子查询?:如果直接用annotate()关联Template和Order,会因为一个Product对应多个Template、一个Template对应多个Order,产生笛卡尔积导致重复计算,最终SUM的结果会偏大。而子查询是针对每个Product单独计算对应的订单总重量,完全不会有重复计数的问题。
- 表名替换提示:代码里的
your_app要换成你实际的app名称,比如你的app叫store,那表名就是store_order、store_template、store_product。 - 空值处理:如果某个Product过去7天没有销售记录,
total_sales_weight会是None,你可以在后续逻辑里处理成0或者其他默认值。
验证用法示例
拿到查询集后,直接访问属性就能获取数值:
for product in products_with_sales: print(f"{product.name}: {product.total_sales_weight or 0} kg")
内容的提问来源于stack exchange,提问作者bdoubleu
相关产品推荐
相关产品推荐

