[pyar] [django] Consulta en tabla grande con annotate y Sum

fisa fisadev en gmail.com
Mie Dic 17 12:49:31 ART 2014


Vas a usar los otros campos? porque por SQL (y no por el ORM de django),
necesitás incluir en el group by cualquier cosa que después vayas a
necesitar leer en los resultados de la query.

Si solo vas a leer pocos campos y al resto no los necesitás, lo primero que
se me ocurrió fue usar only o defer, de esta forma (imaginando que solo te
interesa nombre y apellido):

Cliente.objects.only('nombre', 'apellido').annotate(
saldo=Sum('movimientos__monto'))
o
Cliente.objects.defer('nombre', 'apellido').annotate(
saldo=Sum('movimientos__monto'))

Pero resulta que el ORM sigue metiendo los campos que no necesita en el
group by, por más que no los devuelva. Esto está mal, se podría levantar un
issue en el issue tracker de django para que lo mejoren.

Pero buenas noticias! lo siguiente que intenté sí funciona correctamente:
usar values. Lo malo es que a diferencia de only y defer (donde el ORM te
sigue devolviendo instancias de tu modelo), con values vas a obtener una
lista de diccionarios. Con values lo harías de esta forma:

Cliente.objects.values('nombre', 'apellido').annotate(
saldo=Sum('movimientos__monto'))

Pero más buenas noticias! Como eso genera una query que el ORM
tranquilamente puede usar en raw, lo que finalmente podés hacer para tener
la chancha y los 20 (o sea, hacer la query óptima, y que te devuelva
instancias de Movimiento), es esto:

Cliente.objects.raw(str(Cliente.objects.values('nombre',
'apellido').annotate(saldo=Sum('movimientos__monto')).query))

No es super elegante, pero hace las dos cosas, optimiza la query y devuelve
objetos Movimiento.

Saludos!


On Wed Dec 17 2014 at 11:07:15 AM Matias Varela <matu.varela en gmail.com>
wrote:

> El 06/12/14 12:30, Ariel Rossanigo escribió:
>
> +1 a lo que dice Juan Carlos, las 3 queries son distintas. Fijate que por
> ejemplo la 1 y 2 no tienen el limite de la cantidad de clientes.
>
>
>
> Ademas de que las queries son distintas, la forma de tratar los datos en
> Python es distinta.
> La 1 genera una lista de diccionarios, la 2 una lista de objetos y la
> tercera una lista de tuplas.
>
>  La 3er opción es la más rápida porque trae solo 3000 clientes y además
> no tiene el overhead de generar objetos.
>
>  ¿Vos necesitas los clientes aún cuando el saldo es 0?
> Si es así tenes que partir de "Cliente" como proponías inicialmente y ver
> donde radica el problema de performance. Si en la base de datos está todo
> medianamente bien hecho, el motor solo debiera elegir si le conviene hacer
> un barrido de la tabla de movimientos o no. Esto lo podes ver  en el plan
> de ejecucion del motor (copias la query que genera el ORM, vas a psql y la
> ejecutas anteponiendo EXPLAIN o, si se puede por cuestiones de tiempo,
> EXPLAIN ANALYZE). Si necesitas una mano con esto ultimo copia el resultado
> en [0] y pasame el link.
>
>  Además de esto, si vas a tener 8M de movimientos y la consulta la vas a
> ejecutar periodicamente, te convendría investigar si tenes bien "tuneada"
> la db. Igual esto ya es medio OT...
>
>  Saludos
>  Ariel
>
>  [0]: http://explain.depesz.com/
>
> Buenos días!
>
> Sigo con este tema. Definitivamente necesito un QuerySet de esto, ya que
> usamos django-tables2 y django-filter que esperan un QuerySet.
> Entonces, la consulta la hago así:
>
>     queryset = Cliente.objects.annotate(saldo=Sum('movimientos__monto'))
>
> En la db:
>
> SELECT
> "core_cliente"."id", "core_cliente"."creado_el",
> "core_cliente"."modificado_el",
> "core_cliente"."nombre", "core_cliente"."apellido",
> "core_cliente"."fecha_nacimiento",
> "core_cliente"."genero", "core_cliente"."estado_civil",
> "core_cliente"."dni",
> "core_cliente"."domicilio", SUM("core_movimientocliente"."monto") AS
> "saldo"
> FROM "core_cliente"
> LEFT OUTER JOIN "core_movimientocliente" ON ( "core_cliente"."id" =
> "core_movimientocliente"."cliente_id" )
> GROUP BY "core_cliente"."id", "core_cliente"."creado_el",
> "core_cliente"."modificado_el", "core_cliente"."nombre",
> "core_cliente"."apellido", "core_cliente"."fecha_nacimiento",
> "core_cliente"."genero", "core_cliente"."estado_civil",
> "core_cliente"."dni", "core_cliente"."domicilio"
>
> Lo que claramente está mal es el group by por todos los campos. En la base
> de datos solo hay 3k clientes con 150K movimientos, y está tardando unos
> 44759 ms aprox.
>
> Ariel, hice el analize explain y lo pegué en [0].
>
> ¿Como le especifico al orm que no haga el group by por todo?
>
> Gracias..
>
> [0] http://explain.depesz.com/s/Eqv
>
>
> --
> *Matías E. Varela*
> Skype: matu.varela
> Jadder: matuu en python.org.ar
>
>  _______________________________________________
> pyar mailing list pyar en python.org.ar
> http://listas.python.org.ar/listinfo/pyar
>
> PyAr - Python Argentina - Sitio web: http://www.python.org.ar/
>
> La lista de PyAr esta Hosteada en USLA - Usuarios de Software Libre de
> Argentina - http://www.usla.org.ar
------------ próxima parte ------------
Se ha borrado un adjunto en formato HTML...
URL: <http://listas.python.org.ar/pipermail/pyar/attachments/20141217/d1b75793/attachment-0001.html>


More information about the pyar mailing list