Successfully reported this slideshow.
Your SlideShare is downloading. ×

Exprimiendo el ORM de Django

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Loading in …3
×

Check these out next

1 of 33 Ad

Exprimiendo el ORM de Django

Download to read offline

¿No consigues expresar tus consultas con el ORM de Django? Si ves la necesidad de usar el método extra() casi cada vez que tienes que expresar una query, si aún no tienes claro para qué sirven Q() y F() o si, una vez te has desesperado, acabas escribiendo tus consultas en SQL plano y usando cursores, es probable que esto te interese.

¿No consigues expresar tus consultas con el ORM de Django? Si ves la necesidad de usar el método extra() casi cada vez que tienes que expresar una query, si aún no tienes claro para qué sirven Q() y F() o si, una vez te has desesperado, acabas escribiendo tus consultas en SQL plano y usando cursores, es probable que esto te interese.

Advertisement
Advertisement

More Related Content

Similar to Exprimiendo el ORM de Django (20)

Recently uploaded (20)

Advertisement

Exprimiendo el ORM de Django

  1. 1. Exprimiendo el ORM de Django Héctor Pablos López PyconES 2017
  2. 2. #whoami Héctor Pablos López Frontend developer @ StyleSage www.stylesage.co hector@stylesage.co
  3. 3. Contenido • Introducción • Expresiones (Query expressions) • Joining • Cuando todo lo demás falla
  4. 4. Introducción
  5. 5. Introducción Why I hate the Django ORM: YouTube Video https://www.youtube.com/watch?v=GxL9MnWlCwo Rant alert
  6. 6. Introducción Why I hate the Django ORM: YouTube Video https://www.youtube.com/watch?v=GxL9MnWlCwo Alex Gaynor Desarrollador del ORM de Django
  7. 7. Introducción • Es agnóstico en cuanto al motor de BD • Escribimos consultas en alto nivel… Y en Python! • Completamente integrado en Django (Migraciones, modelos…) ¿Por qué usarlo?
  8. 8. Introducción • El ORM como a mi me hubiera gustado que me lo explicaran • Ejemplos en Python Y SQL Objetivo de esta charla
  9. 9. Introducción Car.ojects.values(‘model_name’).filter(build_year__gt=1995) SELECT car.model_name FROM car WHERE car.build_year > 1995; Expresión básica
  10. 10. Expresiones
  11. 11. Expresiones ¿Qué son? Un valor (simple o calculado) que se puede usar en una orden de DML .filter(expression) .order_by(expression) .annotate(expression) .aggregate(expression)
  12. 12. Expresiones Objetos F() Representan el valor de un campo Car.ojects.filter(units_produced__gt=F(‘units_sold’)) SELECT * FROM car WHERE car.units_produced > car.units_sold
  13. 13. Expresiones Objetos Q() Encapsulan colecciones de argumentos con nombre Car.ojects.filter(Q(kilometers__gt=1000) | Q(build_year__lt=1990)) SELECT * FROM car WHERE car.kilometers > 1000 OR car.build_year < 1990
  14. 14. Expresiones annotate() Anotar (dar nombre) a una expresión Car.objects.annotate(plate=‘plate_number’) SELECT plate_number AS plate FROM car
  15. 15. Expresiones Func() y Aggregate() Funciones de nuestra base de datos… Las que queramos! class DaysBetween(Func): function = ‘DAYS_BETWEEN’ class Now(Func): function = ‘NOW' class Median(Aggregate): function = ‘MEDIAN'
  16. 16. Expresiones Combinar expresiones
  17. 17. Expresiones Combinar expresiones Funciones y más funciones Company.objects .annotate(number_of_days=DaysBetween(Now(), F(‘date_started’))) .filter(number_of_days__lt=100) SELECT DAYS_BETWEEN(NOW(), date_Started) AS number_of_days FROM company WHERE DAYS_BETWEEN(NOW(), date_Started) < 100;
  18. 18. Expresiones Combinar expresiones Funciones y más funciones… Agregadas! Company.objects.values(‘region_id’) .annotate(all_the_days=Sum(DaysBetween(Now(), F(‘date_started’)))) SELECT SUM(DAYS_BETWEEN(NOW(), date_Started)) AS all_the_days FROM company GROUP BY region_id;
  19. 19. Expresiones Combinar expresiones Expresiones condicionales Product.objects.annotate( discounted_product_count=Count( Case( When(is_discounted=True, sold_out=False, then=F('id')), default=None, output_field=IntegerField() ), distinct=True ) )
  20. 20. Expresiones Combinar expresiones Product.objects.annotate( discounted_product_count=Count( [ … ] ), avg_discount=Sum( Case( When(is_discounted=True, sold_out=True, then=F('discount_percent')), default=0, output_field=FloatField() ) ) / Case( When(Q(discounted_product_count__gt=0), then=F('discounted_product_count')), default=1, output_field=IntegerField() ) / 100 )
  21. 21. Expresiones Combinar expresiones
  22. 22. Joining
  23. 23. Joining Con Foreign Key class Parameter(models.Model): name = models.CharField(max_length=30, unique=True) parameter_category = models.ForeignKey(ParameterCategory) affected_entities = models.ManyToManyField( Entity, blank=False, help_text=‘Entities affected by this’, ) Parameter.objects .values(‘name', 'parameter_category__name') SELECT "parameter"."name", "parametercategory"."name" FROM "parameter" INNER JOIN "parametercategory" ON ("parameter"."parameter_category_id" = "parametercategory"."id");
  24. 24. Joining Con Foreign Key class Parameter(models.Model): name = models.CharField(max_length=30, unique=True) parameter_category = models.ForeignKey(ParameterCategory) affected_entities = models.ManyToManyField( Entity, blank=False, help_text=‘Entities affected by this’, ) Parameter.objects.values('name', ‘affected_entities__name') SELECT "parameter"."name", “entity"."name" FROM "parameter" LEFT OUTER JOIN “affected_entities" ON ("parameter"."id" = "affected_entities"."parameter_id") LEFT OUTER JOIN “entity" ON ("affected_entites"."entity_id" = "entity"."id")
  25. 25. Joining Foreign keys: select_related() >> parameters = list( Parameter.objects.values(‘name', ‘parameter_category__name’) ) >> # Lista de diccionarios >> parameters[0][‘parameter_category__name’] u’Name of the category’ >> parameters = list( Parameter.objects.select_related(‘parameter_category’) ) >> # Lista de modelos. Accedemos a parameter_category sin consulta >> # adicional >> parameters[0].parameter_category.name u’Name of the category’
  26. 26. Joining ManyToMany fields: prefetch_related() >> parameters = list( Parameter.objects.values(‘name', ‘affected_entities__name’) ) >> # Lista de diccionarios >> parameters[0][‘affected_entities__name’] u’Name of the entity’ >> parameters = list( Parameter.objects.prefetch_related(‘affected_entities’) ) >> # Lista de modelos. Accedemos a la lista de entidades sin consulta >> # adicional >> parameters[0].affected_entities.all()[0].name u’Name of the category’
  27. 27. Joining Sin Foreign Key ¯_(ツ)_/¯ Método extra()
  28. 28. Joining Sin Foreign Key… Pero con modelos no gestionados por Django class TableWithoutForeignKey(models.Model): not_real = models.ForeignKey(OtherTable, db_column=‘other_column') foo = models.CharField(max_length=50) bar = models.IntegerField() class Meta: db_table = ‘table_without_foreign_key’ managed = False
  29. 29. Si todo lo demás falla
  30. 30. Si todo lo demás falla RawSQL() queryset.annotate( val=RawSQL("select col from sometable where othercol = %s", (someparam,)) ) Añadir subqueries dentro del select
  31. 31. Si todo lo demás falla extra() Joins sin Foreign Key QuerysetTable.extra( where=[ 'table_to_join_with.id = queryset_table.table_to_join_with_id' ], tables = ['table_to_join_with'], )
  32. 32. Si todo lo demás falla Cursores connection = connections[‘connnection_in_settings'] cursor = connection.cursor() sql = """ SELECT some_data, some_other_data, a_thing, footer FROM THE_TABLE WHERE id=%s AND code=%s ORDER BY column_to_order_by; """ cursor.execute(sql, [table_id_to_get, table_code_to_get]) desc = cursor.description the_data_list = [ dict(zip([col[0].lower() for col in desc], row)) for row in cursor.fetchall() ]
  33. 33. #whoami Héctor Pablos López Frontend developer @ StyleSage www.stylesage.co hector@stylesage.co

Editor's Notes

  • Por qué odio el ORM de Django. En él ponen ejemplo de una agrupación muy sencilla que no se podía hacer en el momento de su charla, ahora sí se puede, pero a mi, personalmente, me molesta especialmente no poder hacer un join de dos tablas sin haber definido una foreign key para ellas. Pero os preguntaréis por qué comento este vídeo, si podría ser sólo un usuario cabreado, pues bien…
  • Porque el autor de la charla es este chico Alex Gaynor, quien tiene una gran carrera, siendo uno de sus trabajos… Desarrollar el ORM de Django
  • 1 - Es agnóstico en cuanto al motor de BD, es decir, podemos cambiar nuestro motor de base de datos y tendremos un impacto mínimo, o incluso ningún impacto en nuestro código. Es un poco mentira, porque luego intercambiar bases de datos sin ningún problema no es tan fácil.

    2 - Escribimos consultas en alto nivel: Por ejemplo, podemos beneficiarnos del control de flujo de un lenguaje de programación de alto nivel para construir consultas de una manera mucho más sencilla que concatenando cadenas manualmente a una sentencia sql. Y ejecución lazy, y demás…

    3 - Podemos gestionar la estructura de nuestra base de datos desde nuestro django, con la ventaja de tener la historia de la base de datos en control de versiones, la facilidad para hacer despliegues con cambios en la base de datos, etc…
  • Es el tutorial de orm 2.0, donde termina el tutorial empieza esta charla.
  • Una base de datos no sólo es capaz de devolvernos los datos que tiene almacenados, por lo que el ORM no es sólo para recibir diccionarios o modelos, también lo podemos utilizar para pedir que sea la base de datos la que procese los datos y nos devuelva resultados. Podemos tener bases de datos de Big Data
  • Las expresiones pueden ser tan sencillas como una cadena que representa un campo del modelo, o pueden volverse tremendamente complejas. Lo que hay dentro de estas funciones son expresiones, pero un aggregate también es una expresión en si misma.
  • F() representa el valor de un campo. Con F, por ejemplo, podemos comprar unos campos con otros, pero también realizar operaciones aritméticas.
  • Encapsulan blablabla no significa nada, es lo que dice la documentación. Pero podemos decir que son trozos del Where, que suena mucho mejor.

    Sobre todo, usados para poder hacer condiciones con ORs.
    Se pueden usar igual que los objetos F(), para representar el valor de un campo.

    https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.Q
    https://docs.djangoproject.com/en/1.11/topics/db/queries/#complex-lookups-with-q
  • Nos sirve para dar nombre a una expresión, que puede ser todo lo compleja que queramos. Pero esto no es sólo útil para hacer más manejable el query set resultante. También sirve para tener un nombre con el que referirnos a esa expresión en cualquier expresión posterior.

    https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.query.QuerySet.annotate
  • También podemos crearnos nuestra propia librería de funciones de la base de datos. La gran mayoría de las funciones que nuestra base de datos tenga las podemos convertir a expresiones Func. También las agregadas. Aquí están ejemplos muy sencillos, pero se puede custodiar completamente el SQL generado por cada función, en caso de que su traducción no sea estándar.

    Eso sí, una vez cambiado el SQL nos podemos olvidar un poco de que nuestro código sea agnóstico respecto a la BD.

    https://docs.djangoproject.com/en/1.11/ref/models/expressions/#func-expressions
  • Vemos que podemos usar funciones. Incluso funciones como parámetros de otras funciones. El ORM de Django se encarga traducir correctamente este SQL. Con annotate le damos un nombre al resultado de la función y lo podremos usar como cualquier otro parámetro. Filtrando, por ejemplo, las empresas que tengan menos de 100 días de existencia.
  • Pero también podemos usar funciones dentro de funciones agregadas, aquí sumaríamos los días que lleva de existencia cada empresa.
  • Y si añadimos las expresiones condicionales tenemos una potencia muy cercana a la de la base de datos. Aquí usamos funciones agregadas (Count), expresiones condicionales (Case y When) y un objeto F.
  • Y si añadimos las expresiones condicionales tenemos una potencia muy cercana a la de la base de datos. Aquí usamos funciones agregadas (Count), expresiones condicionales (Case y When) y un objeto F.

    En vez de traerme 200 millones de productos y calcular, hago los cálculos en la base de datos.
  • Y si añadimos las expresiones condicionales tenemos una potencia muy cercana a la de la base de datos. Aquí usamos funciones agregadas (Count), expresiones condicionales (Case y When) y un objeto F.
  • Hacer joins en tablas referenciadas por claves foráneas o tablas intermedias es extremadamente fácil. simplemente hay que indicar los campos que nos interesan del resto de tablas y Django genera la consulta SQL correcta, con un inner join.

    https://docs.djangoproject.com/en/1.10/ref/models/querysets/#django.db.models.query.QuerySet.select_related
  • Lo mismo con las relaciones many to many. En este caso vemos que, adecuadamente, se hace un outer join. Esto es muy potente y muy fácil de usar, mucho mejor que construir las sentencias SQL como cadenas, debemos aprovecharnos de ello.

    https://docs.djangoproject.com/en/1.10/ref/models/querysets/#django.db.models.query.QuerySet.select_related
  • select_related() hace que, en una sola consulta, obtengamos todos los datos de la tabla relacionada con la categoría del parámetro y lo convierte a model, pudiendo acceder sin hacer queries adicionales a dichos datos

    https://docs.djangoproject.com/en/1.11/ref/models/querysets/#select-related
  • prefetch_related() es lo mismo pero para relaciones many to many. Es muy útil, porque en lugar de tener una lista con información redundante de cada parámetro para cada entidad afectada (el producto cartesiano), tenemos un modelo para cada parámetro, que a su vez contiene una “lista” (ManyRelatedManager, actúa como un queryset) con cada uno de los modelos de entidades relacionados.

    https://docs.djangoproject.com/en/1.11/ref/models/querysets/#prefetch-related
  • Se puede hacer, pero es una solución que requiere escribir SQL. Veremos la mejor opción en la siguiente sección.
  • Muchas veces usamos bases de datos en nuestra app de Django que no se gestionan desde la app de Django. Es decir, Django no se encarga del DDL, no crea las tablas ni las modifica, incluso puede que no escriba, sólo lee datos. En este caso… Podemos definir la Foreign Key que queramos! Incluso aunque no sea real.
  • Ejemplo de la documentación, perdemos agnosticismo con la base de datos, debemos usarlo con mucho cuidado si usamos alias de tablas y debemos escapar los parámetros si vienen del exterior.
  • Con extra podemos, por ejemplo, hacer joins entre tablas que no tienen definida una foreign key en el modelo. Las condiciones del where, de hecho, pueden ser varias.

    El método extra() lleva siendo desaconsejado por los desarrolladores de Django desde hace mucho tiempo, se dice que se va a deprecar, pero nunca se hace, ya que es necesario para este tipo de casos.
  • La opción última es ejecutar SQL plano, sin más mediante un cursor. En las líneas resaltada en amarillo cogemos la descripción del cursor (nombres de las columnas) para crear una lista de diccionarios, de la misma forma que si ejecutáramos un QuerySet en lugar de un cursor.

×