[英]How to annotate a Django QuerySet aggregating annotated Subquery
I have a few Django models with a FK relationship between them:我有几个 Django 模型,它们之间存在 FK 关系:
from django.db import models
class Order(models.Model):
notes = models.TextField(blank=True, null=True)
class OrderLine(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField()
price = models.DecimalField(max_digits=8, blank=True, decimal_places=2)
Given an OrderLine
you can calculate its total as quantity by price:给定一个
OrderLine
您可以按价格计算其总数量:
def get_order_line_total(order_line):
return order_line.quantity * order_line.price
Given an Order
you can calculate its total as the sum of its order lines totals:给定一个
Order
您可以将其总计计算为订单行总计的总和:
def get_order_total(order):
order_total = 0
for orderline_for in order.orderline_set.all():
order_total += (order_line_for.quantity * order_line_for.price)
return order_total
I want to annotate that totals in querysets so I can filtrate them, sort them, etc.我想在查询集中注释总计,以便我可以过滤它们,对它们进行排序等。
For the OrderLine
models I found it pretty straight forward:对于
OrderLine
模型,我发现它非常简单:
from django.db.models import F, FloatField, Sum
annotated_orderline_set = OrderLine.objects.annotate(orderline_total=Sum(F('quantity') * F('price'), output_field=FloatField()))
Now I want to annotate the total in an Order.objects
queryset.现在我想在
Order.objects
注释总数。 I guess I would need to use a Subquery but I can't make it work.我想我需要使用子查询,但我不能让它工作。 My guess is (Not working):
我的猜测是(不工作):
from django.db.models import F, FloatField, OuterRef, Subquery, Sum
Order.objects.annotate(
order_total=Subquery(
OrderLine.objects.filter(
order=OuterRef('pk')
).annotate(
orderline_total=Sum(F('quantity') * F('price'), output_field=FloatField())
).values(
'orderline_total'
).aggregate(
Sum('orderline_total')
)['orderline_total__sum']
)
)
# Not working, returns:
# ValueError: This queryset contains a reference to an outer query and may only be used in a subquery.
How could I solve this?我怎么能解决这个问题?
As @aedry comment pointed out, a very simple solution avoiding the Subquery is:正如@aedry 评论指出的,避免子查询的一个非常简单的解决方案是:
Order.objects.annotate(total=models.Sum(F('orderline_set__quantity') * F('orderline_set__price'), output_field=models.DecimalField(max_digits=10, decimal_places=2)))
(I applied the output_field=DecimalField
idea from @Todor answer for type consistency) (我应用了 @Todor 答案中的
output_field=DecimalField
想法以实现类型一致性)
You cannot use .aggregate
because this evaluates the queryset
immediately while you need this evaluation to be delayed until the outer query is being evaluated.您不能使用
.aggregate
因为这个评估queryset
马上,而你需要推迟本次评测,直到外部查询正在评估。
So the correct approach is to .annotate
instead of .aggregate
.所以正确的方法是
.annotate
而不是.aggregate
。
class OrderQuerySet(models.QuerySet):
def annotate_total(self):
return self.annotate(
total=models.Subquery(
OrderLine.objects.filter(
order=models.OuterRef('pk')
).annotate_total()
.values('order')
.annotate(total_sum=models.Sum('total'))
.values('total_sum')
)
)
class Order(models.Model):
# ...
objects = OrderQuerySet.as_manager()
class OrderLineQuerySet(models.QuerySet):
def annotate_total(self):
return self.annotate(
total=models.ExpressionWrapper(
models.F('quantity')*models.F('price'),
output_field=models.DecimalField(max_digits=10, decimal_places=2)
)
)
class OrderLine(models.Model):
#...
objects = OrderLineQuerySet.as_manager()
# Usage:
>>> for l in OrderLine.objects.all().annotate_total():
... print(l.id, l.order_id, l.quantity, l.price, l.total)
...
1 1 3 20.00 60
2 1 9 10.00 90
3 2 18 2.00 36
>>> for o in Order.objects.all().annotate_total():
... print(o.id, o.total)
...
1 150
2 36
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.