¿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.
5. Introducción
Why I hate the Django ORM: YouTube Video
https://www.youtube.com/watch?v=GxL9MnWlCwo
Rant alert
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. 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. Introducción
• El ORM como a mi me hubiera gustado que me lo
explicaran
• Ejemplos en Python Y SQL
Objetivo de esta charla
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. 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. 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
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'
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. 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;
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. 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. 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. 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’
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
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. 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. 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()
]
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.