2012-04-19 9 views
7

Tengo un modelo XYZ y necesito obtener el valor máximo para los campos a, b y la expresión x/y para un conjunto de preguntas dado.Consultas agregadas de Django con expresiones

Funciona muy bien para los campos. Algo así como:

>>> XYZ.all().aggregate(Max('a')) 

... {'a__max': 10} 

Sin embargo, no puedo encontrar la manera de hacerlo por expresiones. Intentar algo así como:

>>> XYZ.all().aggregate(Max('x/y')) 

da un error:

*** FieldError: Cannot resolve keyword 'x/y' into field. Choices are: a, b, x, y, id 

tratando algo como:

>>> XYZ.all().aggregate(Max(F('x')/F('y'))) 

da un error:

*** AttributeError: 'ExpressionNode' object has no attribute 'split' 

E incluso algo como:

XYZ.all().extra(select={'z':'x/y'}).aggregate(Max('z')) 

tampoco funciona y da el mismo error que el anterior:

FieldError: Cannot resolve keyword 'z' into field. Choices are: a, b, x, y, id 

El truco que he encontrado para hacerlo es:

XYZ.all().extra(select={'z':'MAX(x/y)'})[0].z 

Qué realmente funciona, ya que genera el SQL correcto, pero es confuso porque obtengo el valor correcto en el atributo z, pero no en el caso correcto, el que tiene ese valor máximo.

Por supuesto, también podría usar consultas crudas o trucos con extra() y order_by(), pero realmente no tiene sentido para mí que Django soporte todas las consultas agregadas de una manera agradable, pero no puede soportar expresiones incluso con sus propias expresiones F.

¿Hay alguna manera de hacerlo?

+0

Usted puede estar interesado en saber que la capacidad de utilizar 'F()' objetos en los agregados es [parte de la próxima versión de Django 1.8] (https://code.djangoproject.com/ wiki/Version1.8Roadmap). –

Respuesta

6

En SQL, lo que quiere es en realidad

SELECT x/y, * FROM XYZ ORDER BY x/y DESC LIMIT 1; 
# Or more verbose version of the #1 
SELECT x/y, id, a, b, x, y FROM XYZ GROUP BY x/y, id, a, b, x, y ORDER BY x/y DESC LIMIT 1; 
# Or 
SELECT * FROM XYZ WHERE x/y = (SELECT MAX(x/y) FROM XYZ) LIMIT 1; 

Así, en Django ORM:

XYZ.objects.extra(select={'z':'x/y'}).order_by('-z')[0] 
# Or 
XYZ.objects.extra(select={'z':'x/y'}).annotate().order_by('-z')[0] 
# Or x/y=z => x=y*z 
XYZ.objects.filter(x=models.F('y') * XYZ.objects.extra(select={'z':'MAX(x/y)'})[0].z)[0] 

La versión

XYZ.all().extra(select={'z':'MAX(x/y)'})[0].z 

no tiene correcta x, y y ejemplo porque la función MAX se evalúa entre todas las filas, cuando no hay GROUP BY, por lo tanto todos los casos en el QuerySet vuelto tendrá mismo valor de z como MAX(x/y).

+0

Correcto, pero la intención es obtener el valor máximo en sí mismo, como el retorno de XYZ.all(). aggregate (Max ('a')), no la instancia que lo contiene. La versión con extra-select es la más cercana. No devolver la instancia correcta es un efecto secundario confuso, pero devuelve el valor correcto. Como dije en el mensaje de apertura, conozco las soluciones con extra y order_by, pero no son aceptables, ya que requieren un tipo completo de la tabla, no un solo pase. No tiene mucho sentido que Django admita Max aggregate con campos individuales, pero no con expresiones. –

+0

@pjwerneck La razón del efecto secundario confuso que ha llamado, se describe en el último párrafo de mi respuesta. Si solo quiere el valor máximo, 'XYZ.objects.extra (select = {'z': 'MAX (x/y)'}) [0] .z' es suficiente, no hay order_by. O incluso directamente 'cursor.execute ('SELECT MAX (x/y) from XYZ')'. Estoy de acuerdo con que Django no proporciona expresiones agregadas con w /, porque podría ser mucho más difícil que apoyar la OMI de campo único. – okm

-3

creo que debe obtener los valores máximos separado

result = XYZ.aggregate(Max('x'), Max('y')) 

y luego dividir los dos campos

result['x__max'] \ result['y__max'] 
+0

Eso no tiene ningún sentido. Incluso si devuelve la fila con los pares max x e y, no es necesariamente el máximo x/y. Por ejemplo, la fila Max (x)/Max (y) es 69/16 = 4, mientras que max (x/y) es 8/1 = 8 –

2

Su ejemplo que usa objetos F() debería funcionar bien desde Django 1.8:

XYZ.all().aggregate(Max(F('x')/F('y'))) 

Hay un fragmento que demuestra la agregación con Sum() y F() objetos en el Django aggregation cheat sheet:

Book.objects.all().aggregate(price_per_page=Sum(F('price')/F('pages')) 
+0

Es bueno saberlo. ¡Gracias! –

0

Para versiones anteriores a la 1.8 que puede lograr el mismo de esta manera (no documentado).

Book.objects.all().aggregate(price_per_page=Sum('price_per_page', 
               field='book_price/book_pages')) 

Esto funciona para Postgres, no sé sobre MySQL.

Fuente: Django Aggregation: Summation of Multiplication of two fields

Cuestiones relacionadas