2009-04-30 18 views
32

tengo tres modelos simplificados para el ejemplo:Anotar una suma de dos campos multiplicado

class Customer(models.Model): 
    email = models.CharField(max_length=128) 

class Order(models.Model): 
    customer = models.ForeignKey(Customer) 
    order_status = models.CharField(blank=True, max_length=256) 

class Lineitem(models.Model): 
    order = models.ForeignKey(Order) 
    quantity = models.IntegerField(blank=True) 
    price = models.DecimalField(max_digits=6, decimal_places=2) 

Quiero consultar los clientes (posiblemente con un filtro) y anotar el total de ellos han pasado (es decir, la suma más (precio * cantidad)

he intentado:.?
Customer.objects.filter(something).annotate(total_spent=Sum(F('order__lineitem__quantity') * F('order__lineitem__price')))

parecería que suma() no se puede utilizar con F() expresiones ¿hay otra manera de hacer esto

+0

¿Alguna vez conseguir que esto funcione? – dotty

+0

Estoy buscando una solución también – Don

+0

Posible duplicado de [Agregación de Django: Suma de multiplicación de dos campos] (http://stackoverflow.com/questions/12165636/django-aggregation-summation-of-multiplication-of- two -fields) – Louis

Respuesta

1

¿Ha mirado utilizando el método .extra()?

Ver el Django QuerySet API.

+3

Tengo. Funciona, pero trato de evitarlo por dos razones: en primer lugar, utiliza una subconsulta por fila en lugar de una combinación, que puede escalar mal para algunos servidores de bases de datos.En segundo lugar, no funciona con filter() en el campo extra, por lo que no se puede combinar de forma procedimental con otros objetos Q – Sam

1

Usted podría tratar de usar una propiedad en el modelo LineItem:

class Lineitem(models.Model): 
    order = models.ForeignKey(Order) 
    quantity = models.IntegerField(blank=True) 
    price = models.DecimalField(max_digits=6, decimal_places=2) 
    def _get_total(self): 
     return quantity * price 
    total = property(_get_total) 

entonces creo que puede anotar con la cantidad total en el uso de

Customer.objects.filter(something).annotate(total_spent=Sum('order__lineitem__total')) 

No sé cómo la eficiencia de este método se relaciona con otros, pero es más Pythonic/Django-y que la alternativa, que es escribir toda la consulta SQL a mano como en

Customer.objects.raw("SELECT ... from <customer_table_name> where ...") 
+0

Lo he intentado y no funciona, obtengo la palabra clave 'No se puede resolver en error de campo :-( –

+0

OK, del comentario sobre la otra respuesta parece que esto no se puede hacer con anotar en una propiedad. – murgatroid99

+0

El ORM de Django genera consultas que realizan cálculos a través de SQL y, por lo tanto, no es posible incluir propiedades definidas por Python en métodos como parte de una consulta. Puede aplicar una lista de comprensión posterior a la consulta, pero esto no es lo mismo, como señaló Justin Hamade. – acjay

1

Me acabo de encontrar con esto y no creo que anotar y funcionará con una propiedad, consulte Django - Can you use property as the field in an aggregation function?

Aquí es lo que hice.

class Customer(models.Model): 
    email = models.CharField(max_length=128) 

class Order(models.Model): 
    customer = models.ForeignKey(Customer) 
    order_status = models.CharField(blank=True, max_length=256) 

class Lineitem(models.Model): 
    order = models.ForeignKey(Order) 
    quantity = models.IntegerField(blank=True) 
    price = models.DecimalField(max_digits=6, decimal_places=2) 
    @property 
    def total(self): 
     return self.quantity * self.price 

A continuación, utilice la suma y una lista por comprensión:

sum([li.total for li in LineItem.objects.filter(order__customer=some_customer).filter(somefilter)]) 
+0

que no funciona porque las propiedades se calculan después de la consulta –

+0

Esta opción funciona pero estás haciendo la suma en python no sql, lo que significa que será mucho más lento a medida que crezca. –

+0

¿Este método se actualiza a medida que agregas más órdenes? – agconti

0

similares a: https://stackoverflow.com/a/19888120/1344647

from django.db.models import Sum 

q = Task.objects.filter(your-filter-here).annotate(total=Sum('progress', field="progress*estimated_days")) 

Editado: Gracias a @Max, mediante anotaciones en vez agregada.

+1

de manera correcta: pero en lugar de uso agregado anota y elimina [ 'total'] En resultado: total = Task.objects.filter (su-fi lter-here) .annotate (total = Sum ('progress', field = "progress * estimated_days")). Rehacer el ejemplo para ser positivo – Max

6

Tal vez usted no necesita esta respuesta ahora, pero si usted lee la documentación sobre Sum expression, tiene que declarar la output_field, así:

Customer.objects.filter(something) 
       .annotate(total_spent=Sum(
        F('order__lineitem__quantity') * 
        F('order__lineitem__price'), 
        output_field=models.FloatField() 
       )) 
Cuestiones relacionadas