简体   繁体   English

如何注释 Django QuerySet 聚合注释子查询

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM