Las expresiones de consulta describen un valor o una computación que se puede utilizar como parte de una actualización, creación, filtro, orden por, anotación o agregado. Cuando una expresión produce un valor booleano, se puede utilizar directamente en los filtros. Hay varias expresiones integradas (documentadas a continuación) que se pueden utilizar para ayudarlo a escribir consultas. Las expresiones se pueden combinar, o en algunos casos anidar, para formar computaciones más complejas.
Django admite negación, suma, resta, multiplicación, división, módulo y el operador de potencia sobre las expresiones de consulta, utilizando constantes y variables de Python, e incluso otras expresiones.
Muchas de las expresiones documentadas en esta sección admiten un parámetro output_field opcional. Si se proporciona, Django cargará el valor en ese campo después de recuperarlo de la base de datos.
output_field toma una instancia de campo del modelo, como IntegerField() o BooleanField(). Normalmente, el campo no necesita argumentos adicionales, como max_length, ya que los argumentos de campo se relacionan con la validación de datos, que no se realizará en el valor de salida de la expresión.
output_field solo es necesario cuando Django no puede determinar automáticamente el tipo del resultado del campo, como expresiones complejas que mezclen tipos de campos. Por ejemplo, sumar un DecimalField() y un FloatField() requiere un campo de salida, como output_field=FloatField().
output_field también permite utilizar campos personalizados que realizan conversiones de tipo fuera del contexto de un campo del modelo específico. Por ejemplo, si frecuentemente necesita realizar aritmética con fechas utilizando timedelta, puede crear un campo personalizado que maneje la conversión, asegurando resultados consistentes en todas las bases de datos. Consulte Cómo crear campos de modelo personalizados.
>>> from django.db.models import Count, F, Value
>>> from django.db.models.functions import Length, Upper
>>> from django.db.models.lookups import GreaterThan
# Find companies that have more employees than chairs.
>>> Company.objects.filter(num_employees__gt=F("num_chairs"))
# Find companies that have at least twice as many employees
# as chairs. Both the querysets below are equivalent.
>>> Company.objects.filter(num_employees__gt=F("num_chairs") * 2)
>>> Company.objects.filter(num_employees__gt=F("num_chairs") + F("num_chairs"))
# How many chairs are needed for each company to seat all employees?
>>> company = (
... Company.objects.filter(num_employees__gt=F("num_chairs"))
... .annotate(chairs_needed=F("num_employees") - F("num_chairs"))
... .first()
... )
>>> company.num_employees
120
>>> company.num_chairs
50
>>> company.chairs_needed
70
# Create a new company using expressions.
>>> company = Company.objects.create(name="Google", ticker=Upper(Value("goog")))
# Be sure to refresh it if you need to access the field.
>>> company.refresh_from_db()
>>> company.ticker
'GOOG'
# Annotate models with an aggregated value. Both forms
# below are equivalent.
>>> Company.objects.annotate(num_products=Count("products"))
>>> Company.objects.annotate(num_products=Count(F("products")))
# Aggregates can contain complex computations also
>>> Company.objects.annotate(num_offerings=Count(F("products") + F("services")))
# Expressions can also be used in order_by(), either directly
>>> Company.objects.order_by(Length("name").asc())
>>> Company.objects.order_by(Length("name").desc())
# or using the double underscore lookup syntax.
>>> from django.db.models import CharField
>>> from django.db.models.functions import Length
>>> CharField.register_lookup(Length)
>>> Company.objects.order_by("name__length")
# Boolean expression can be used directly in filters.
>>> from django.db.models import Exists, OuterRef
>>> Company.objects.filter(
... Exists(Employee.objects.filter(company=OuterRef("pk"), salary__gt=10))
... )
# Lookup expressions can also be used directly in filters
>>> Company.objects.filter(GreaterThan(F("num_employees"), F("num_chairs")))
# or annotations.
>>> Company.objects.annotate(
... need_chairs=GreaterThan(F("num_employees"), F("num_chairs")),
... )
Nota
Estas expresiones se definen en django.db.models.expressions y django.db.models.aggregates, pero por conveniencia están disponibles y suelen importarse desde django.db.models.
Un objeto F() representa el valor de un campo del modelo, la transformación del valor de un campo del modelo o una columna anotada. Hace posible referirse a los valores de los campos del modelo y realizar operaciones en la base de datos utilizandolos sin tener que extraerlos realmente de la base de datos en memoria de Python.
En su lugar, Django utiliza el objeto F() para generar una expresión SQL que describe la operación requerida a nivel de la base de datos.
Vamos a intentarlo con un ejemplo. Normalmente, uno podría hacer algo como esto:
# Tintin filed a news story!
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed += 1
reporter.save()
Aquí, hemos extraído el valor del campo reporter.stories_filed de la base de datos en memoria y lo hemos manipulado utilizando operadores Python familiares, y luego hemos guardado el objeto en la base de datos. Pero en su lugar también podríamos haber hecho:
from django.db.models import F
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F("stories_filed") + 1
reporter.save()
Aunque reporter.stories_filed = F('stories_filed') + 1 parece una asignación normal de valor a un atributo de instancia en Python, en realidad es un constructo SQL que describe una operación en la base de datos.
Cuando Django encuentra una instancia de F(), sobrescribe los operadores estándar de Python para crear una expresión SQL encapsulada; en este caso, una que instruye a la base de datos a incrementar el campo de la base de datos representado por reporter.stories_filed.
Cualquier valor que sea o haya sido en reporter.stories_filed, Python nunca llega a saberlo - se trata completamente por la base de datos. Todo lo que hace Python, a través de la clase F() de Django, es crear el sintaxis SQL para referirse al campo y describir la operación.
Para acceder al valor nuevo guardado de esta manera, el objeto debe ser recargado:
reporter = Reporters.objects.get(pk=reporter.pk)
# Or, more succinctly:
reporter.refresh_from_db()
Además de utilizarse en operaciones sobre instancias individuales como arriba, F() se puede utilizar con update() para realizar actualizaciones en masa sobre un QuerySet. Esto reduce las dos consultas que estábamos utilizando anteriormente - la get() y el save() - a solo una:
reporter = Reporters.objects.filter(name="Tintin")
reporter.update(stories_filed=F("stories_filed") + 1)
También podemos usar update() para incrementar el valor del campo en múltiples objetos - lo que podría ser mucho más rápido que sacarlos todos a Python desde la base de datos, recorrerlos en un bucle, incrementar el valor del campo de cada uno y guardar cada uno nuevamente en la base de datos:
Reporter.objects.update(stories_filed=F("stories_filed") + 1)
F() por tanto puede ofrecer ventajas de rendimiento al:
hacer que la base de datos, en lugar de Python, realice trabajo
reducir el número de consultas que requieren algunas operaciones
F()¶Para campos de texto y campos de texto basados en cadena, así como para ArrayField, puedes utilizar la sintaxis de Python para cortar arrays. Los índices son 0-basados. El argumento step a slice y el índice negativo no están soportados. Por ejemplo:
>>> # Replacing a name with a substring of itself.
>>> writer = Writers.objects.get(name="Priyansh")
>>> writer.name = F("name")[1:5]
>>> writer.save()
>>> writer.refresh_from_db()
>>> writer.name
'riya'
F()¶Otra ventaja útil de F() es que tener la base de datos - en lugar de Python - actualizar un valor de campo evita una condición de carrera.
Si dos hilos de Python ejecutan el código en el ejemplo anterior, uno de los hilos podría recuperar, incrementar y guardar el valor de un campo después de que otro hilo lo haya recuperado de la base de datos. El valor que el segundo hilo guarda será basado en el valor original; el trabajo del primer hilo se perderá.
Si es la base de datos la responsable de actualizar el campo, el proceso es más robusto: solo actualizará el campo basándose en el valor del campo en la base de datos cuando se ejecuta save() o update() en lugar de basarse en su valor cuando se recuperó la instancia.
F() persisten después de Model.save()¶Los objetos F() asignados a campos de modelos persisten después de guardar la instancia del modelo y se aplicarán en cada save(). Por ejemplo:
reporter = Reporters.objects.get(name="Tintin")
reporter.stories_filed = F("stories_filed") + 1
reporter.save()
reporter.name = "Tintin Jr."
reporter.save()
En este caso, stories_filed se actualizará dos veces. Si inicialmente es 1, el valor final será 3. Esta persistencia puede evitarse restando la instancia del modelo después de guardarla, por ejemplo, utilizando refresh_from_db().
F() en filtros¶F() también es muy útil en los filtros de QuerySet, donde hacen posible filtrar un conjunto de objetos contra criterios basados en sus valores de campo, en lugar de en valores Python.
Esto se documenta en usando expresiones F() en consultas.
F() con anotaciones¶F() puede usarse para crear campos dinámicos en sus modelos combinando diferentes campos con aritmética:
company = Company.objects.annotate(chairs_needed=F("num_employees") - F("num_chairs"))
Si los campos que estás combinando tienen tipos diferentes, necesitarás decir a Django qué tipo de campo se devolverá. La mayoría de las expresiones admiten :ref:`output_field<output-field> para este caso, pero ya que F() no lo hace, deberás envolver la expresión con ExpressionWrapper:
from django.db.models import DateTimeField, ExpressionWrapper, F
Ticket.objects.annotate(
expires=ExpressionWrapper(
F("active_at") + F("duration"), output_field=DateTimeField()
)
)
Cuando se hace referencia a campos relacionales como ForeignKey, F() devuelve el valor de la clave primaria en lugar de una instancia del modelo:
>>> car = Company.objects.annotate(built_by=F("manufacturer"))[0]
>>> car.manufacturer
<Manufacturer: Toyota>
>>> car.built_by
3
Usa F() y el argumento de palabra clave nulls_first o nulls_last en la función Expression.asc() o desc() para controlar el ordenamiento de los valores nulos de un campo. Por defecto, el ordenamiento depende de tu base de datos.
Ejemplo de cómo ordenar las empresas que no han sido contactadas (last_contacted es nulo) después de las empresas que sí lo han sido:
from django.db.models import F
Company.objects.order_by(F("last_contacted").desc(nulls_last=True))
Expresiones F() que devuelven un campo de tipo booleano pueden ser lógicamente negadas con el operador de inversión ~F(). Por ejemplo, para intercambiar el estado de activación de las empresas:
from django.db.models import F
Company.objects.update(is_active=~F("is_active"))
Expresiones Func() son el tipo de base de todas las expresiones que involucran funciones del servidor de bases de datos como COALESCE y LOWER, o agregados como SUM. Pueden usarse directamente:
from django.db.models import F, Func
queryset.annotate(field_lower=Func(F("field"), function="LOWER"))
o pueden usarse para construir una biblioteca de funciones de base de datos:
class Lower(Func):
function = "LOWER"
queryset.annotate(field_lower=Lower("field"))
Pero ambos casos darán como resultado un conjunto de consultas donde cada modelo estará anotado con una atributo adicional field_lower producido, aproximadamente, a partir del siguiente SQL:
SELECT
...
LOWER("db_table"."field") as "field_lower"
Ve la documentación sobre funciones de base de datos <doc> database-functions </doc> para una lista de funciones de base de datos integradas.
La API Func es la siguiente:
Una atributo de clase que describe la función que se generará. Específicamente, el function se interpolará como el reemplazo de function dentro de template. Por defecto es None.
Una atributo de clase, como una cadena de formato, que describe la SQL generada para esta función. Por defecto es ``”%(function)s(%(expressions)s)””`.
Si estás construyendo SQL como strftime('%W', 'date') y necesitas un carácter literal % en la consulta, cuadruplica el símbolo (%%%%) en la propiedad template porque la cadena se interpola dos veces: una vez durante la interpolación de plantilla en as_sql() y otra en la interpolación SQL con los parámetros de la consulta en el cursor de base de datos.
Una atributo de clase que denota el carácter utilizado para unir la lista de expressions entre sí. Por defecto, es ', '.
Un atributo de clase que denota el número de argumentos que acepta la función. Si se establece este atributo y se llama a la función con un diferente número de expresiones, se levantará una TypeError. Por defecto es None.
Genera el fragmento de SQL para la función de base de datos. Devuelve una tupla (sql, params), donde sql es la cadena de SQL y params es la lista o tupla de parámetros de consulta.
Los métodos as_vendor() deben utilizar los parámetros function, template, arg_joiner y cualquier otro **extra_context para personalizar la consulta SQL según sea necesario. Por ejemplo:
class ConcatPair(Func):
...
function = "CONCAT"
...
def as_mysql(self, compiler, connection, **extra_context):
return super().as_sql(
compiler,
connection,
function="CONCAT_WS",
template="%(function)s('', %(expressions)s)",
**extra_context
)
Para evitar una vulnerabilidad de inyección SQL, extra_context no debe contener entrada de usuario no confiable ya que estos valores se insertan en la cadena SQL en lugar de pasarlos como parámetros de consulta, donde el controlador del motor de base de datos los escaparía.
El argumento *expressions es una lista de expresiones posicionales a las que se aplicará la función. Las expresiones se convertirán en cadenas, se unirán con arg_joiner y luego se insertarán en el template como reemplazo del marcador de posición expressions.
Los argumentos posicionales pueden ser expresiones o valores de Python. Las cadenas se asumen como referencias a columnas y se envolverán en F() expresiones, mientras que otros valores se envolverán en Value() expresiones.
La traducción es:
La traducción es:
Aggregate()¶Una expresión agregada es un caso especial de una expresión Func() que informa a la consulta que se requiere una cláusula GROUP BY. Todas las funciones de agregado, como Sum() y Count(), heredan de Aggregate().
Puedes representar algunas computaciones complejas ya que las `Aggregate`s son expresiones y envuelven expresiones.
from django.db.models import Count
Company.objects.annotate(
managers_required=(Count("num_employees") / 4) + Count("num_managers")
)
La API Aggregate es la siguiente:
Un atributo de clase, como una cadena de formato, que describe la SQL generada para esta agregación. Por defecto es '%(function)s(%(distinct)s%(expressions)s)'.
Un atributo de clase que describe la función de agregado que se generará. Específicamente, el function se interpolará como el lugar holder function dentro de template. Por defecto es None.
Por defecto es True ya que la mayoría de las funciones de agregado pueden ser utilizadas como expresión fuente en Window.
Un atributo de clase que determina si esta función de agregado permite pasar un argumento distinct. Si se establece a False (por defecto), se levanta una TypeError si se pasa distinct=True.
Por defecto es None ya que la mayoría de las funciones de agregado resultan en NULL cuando se aplican a un conjunto de resultados vacío.
Los argumentos posicionales expressions pueden incluir expresiones, transformaciones del campo del modelo o los nombres de campos del modelo. Se convertirán a una cadena y se utilizarán como el lugar holder expressions dentro de la template.
El argumento distinct determina si la función de agregado debe ser invocada para cada valor distinto de expressions (o conjunto de valores, para múltiples expressions). El argumento solo está disponible en las agregaciones que tienen allow_distinct establecido a True.
El argumento filter toma un objeto Q que se utiliza para filtrar las filas que se agrupan. Consulte agregación condicional y filtrado en anotaciones para ver ejemplos de uso.
El argumento default toma un valor que se pasará junto con la agregación a Coalesce. Esto es útil para especificar un valor que se devuelva diferente de None cuando el conjunto de resultados (o agrupado) contiene no entradas.
Los kwargs **extra son pares key=value que pueden ser interpolados en la atributo template.
También puedes crear tus propias funciones de agregado. Al menos, debes definir function, pero también puedes personalizar completamente el SQL que se genera. Aquí tienes un ejemplo breve:
from django.db.models import Aggregate
class Sum(Aggregate):
# Supports SUM(ALL field).
function = "SUM"
template = "%(function)s(%(all_values)s%(expressions)s)"
allow_distinct = False
arity = 1
def __init__(self, expression, all_values=False, **extra):
super().__init__(expression, all_values="ALL " if all_values else "", **extra)
Un objeto Value() representa la componente más pequeña posible de una expresión: un valor simple. Cuando necesitas representar el valor de un entero, booleano o cadena dentro de una expresión, puedes envolver ese valor en un Value().
Raramente necesitarás usar Value() directamente. Al escribir la expresión F('campo') + 1, Django envuelve implícitamente el 1 en un Value(), lo que permite valores simples ser utilizados en expresiones más complejas. Debes utilizar Value() cuando quieras pasar una cadena a una expresión. La mayoría de las expresiones interpretan una cadena como argumento como el nombre de un campo, como Lower('nombre').
El argumento value describe el valor que se incluirá en la expresión, como 1, True o None. Django sabe cómo convertir estos valores de Python a su tipo correspondiente en la base de datos.
Si no se especifica ningún campo de salida output_field, se inferirá desde el tipo del valor proporcionado para muchos tipos comunes. Por ejemplo, pasar una instancia de datetime.datetime como value establece output_field en DateTimeField por defecto.
ExpressionWrapper rodea otra expresión y proporciona acceso a propiedades, como output_field, que no estén disponibles en otras expresiones. ExpressionWrapper es necesario cuando se utilizan operaciones aritméticas con expresiones F() de diferentes tipos, como se describe en Usando F() con anotaciones.
No se realiza la conversión de base de datos
La traducción de los textos es la siguiente:
Las expresiones condicionales te permiten utilizar lógica if … elif … else en consultas. Django admite nativamente las expresiones SQL CASE. Para más detalles, vea Expresiones condicionales.
Subquery()¶Puedes agregar una subconsulta explícita a un QuerySet utilizando la expresión Subquery.
Por ejemplo, para anotar cada publicación con la dirección de correo electrónico del autor de la última comentario en esa publicación:
>>> from django.db.models import OuterRef, Subquery
>>> newest = Comment.objects.filter(post=OuterRef("pk")).order_by("-created_at")
>>> Post.objects.annotate(newest_commenter_email=Subquery(newest.values("email")[:1]))
En PostgreSQL, el SQL se ve así:
SELECT "post"."id", (
SELECT U0."email"
FROM "comment" U0
WHERE U0."post_id" = ("post"."id")
ORDER BY U0."created_at" DESC LIMIT 1
) AS "newest_commenter_email" FROM "post"
Nota
Los ejemplos de esta sección están diseñados para mostrar cómo forzar a Django a ejecutar una subconsulta. En algunos casos puede ser posible escribir un queryset equivalente que realice la misma tarea con más claridad o eficiencia.
Utiliza OuterRef cuando un queryset en una Subquery necesita referirse a un campo del query externo o su transformación. Actúa como una expresión F excepto que la comprobación para ver si se refiere a un campo válido no se realiza hasta que el queryset externo esté resuelto.
Instances de OuterRef pueden ser utilizados en conjunto con instancias anidadas de Subquery para referirse a un conjunto de objetos que no es el padre inmediato. Por ejemplo, este conjunto de objetos debería estar dentro de una pareja anidada de instancias de Subquery para resolver correctamente:
>>> Book.objects.filter(author=OuterRef(OuterRef("pk")))
Hay ocasiones en las que debe devolverse una sola columna desde una Subquery, por ejemplo, para utilizar una Subquery como objetivo de un lookup __in. Para devolver todos los comentarios de los posts publicados dentro del último día:
>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> posts = Post.objects.filter(published_at__gte=one_day_ago)
>>> Comment.objects.filter(post__in=Subquery(posts.values("pk")))
En este caso, la subconsulta debe usar values() para devolver solo una sola columna: la clave primaria del post.
Para evitar que una subconsulta devuelva múltiples filas, se utiliza un slice ([:1]) del conjunto de objetos:
>>> subquery = Subquery(newest.values("email")[:1])
>>> Post.objects.annotate(newest_commenter_email=subquery)
En este caso, la subconsulta debe devolver solo una columna y una fila: la dirección de correo electrónico del comentario creado más recientemente.
(Usar get() en lugar de un slice fallaría porque el OuterRef no puede ser resuelto hasta que el conjunto de objetos se utilice dentro de una Subquery.)
Exists()¶Exists es una subclase de Subquery que utiliza un SQL EXISTS. En muchos casos, funcionará mejor que una subconsulta ya que la base de datos puede detener la evaluación de la subconsulta cuando se encuentra la primera fila coincidente.
Para anotar cada publicación con si tiene o no un comentario desde hace un día:
>>> from django.db.models import Exists, OuterRef
>>> from datetime import timedelta
>>> from django.utils import timezone
>>> one_day_ago = timezone.now() - timedelta(days=1)
>>> recent_comments = Comment.objects.filter(
... post=OuterRef("pk"),
... created_at__gte=one_day_ago,
... )
>>> Post.objects.annotate(recent_comment=Exists(recent_comments))
En PostgreSQL, el SQL se ve así:
SELECT "post"."id", "post"."published_at", EXISTS(
SELECT (1) as "a"
FROM "comment" U0
WHERE (
U0."created_at" >= YYYY-MM-DD HH:MM:SS AND
U0."post_id" = "post"."id"
)
LIMIT 1
) AS "recent_comment" FROM "post"
No es necesario forzar a Exists que se refiera a una sola columna, ya que las columnas se descartan y se devuelve un resultado booleano. De manera similar, ya que el ordenamiento es innecesario dentro de una subconsulta SQL EXISTS y solo empeoraría la performance, se elimina automáticamente.
Puedes consultar utilizando NOT EXISTS con ~Exists().
Subquery() o Exists()¶Subquery() que devuelve un valor booleano y Exists pueden usarse como una condition en las expresiones When o para filtrar directamente una consulta de conjunto:
>>> recent_comments = Comment.objects.filter(...) # From above
>>> Post.objects.filter(Exists(recent_comments))
Esto asegurará que la subconsulta no se agregue a las columnas SELECT, lo que puede resultar en una mejor performance.
Subquery¶Los agregados pueden usarse dentro de una Subquery, pero requieren una combinación específica de filter(), values() y annotate() para obtener el agrupamiento correcto de la subconsulta.
Suponiendo que ambos modelos tienen un campo length, encontrar publicaciones donde la longitud de la publicación es mayor que la longitud total de todos los comentarios combinados:
>>> from django.db.models import OuterRef, Subquery, Sum
>>> comments = Comment.objects.filter(post=OuterRef("pk")).order_by().values("post")
>>> total_comments = comments.annotate(total=Sum("length")).values("total")
>>> Post.objects.filter(length__gt=Subquery(total_comments))
La inicial filter(...) limita la subconsulta a los parámetros relevantes. order_by() elimina el orden predeterminado ordering (si existe) en el modelo Comment. values('post') agrupa comentarios por Post. Finalmente, annotate(...) realiza la agregación. El orden en que se aplican estos métodos de conjunto es importante. En este caso, ya que la subconsulta debe limitarse a una sola columna, values('total') es necesario.
Esta es la única forma de realizar una agregación dentro de un Subquery, ya que utilizar aggregate() intenta evaluar el conjunto de consultas (y si hay un OuterRef, esto no será posible resolver).
A veces las expresiones de la base de datos no pueden expresar fácilmente una cláusula WHERE compleja. En estos casos límite, utilice la expresión RawSQL. Por ejemplo:
>>> from django.db.models.expressions import RawSQL
>>> queryset.annotate(val=RawSQL("select col from sometable where othercol = %s", (param,)))
Estas consultas adicionales pueden no ser portables a diferentes motores de bases de datos (ya que está escribiendo explícitamente código SQL) y violan el principio DRY, por lo que debe evitarlas si es posible.
Las expresiones RawSQL también se pueden utilizar como objetivo de filtros __in:
>>> queryset.filter(id__in=RawSQL("select id from sometable where col = %s", (param,)))
Advertencia
Para protegerse contra ataques de inyección de SQL <https://en.wikipedia.org/wiki/SQL_injection>, debe escapar cualquier parámetro que el usuario pueda controlar utilizando params. params es un argumento requerido para obligarlo a reconocer que no está interpolando su SQL con datos proporcionados por el usuario.
También no debe colocar marcadores de reemplazo entre comillas en la cadena de SQL. Este ejemplo es vulnerable a inyección de SQL debido a las comillas alrededor de %s:
RawSQL("select col from sometable where othercol = '%s'") # unsafe!
Puede leer más sobre cómo funciona la protección contra inyecciones de SQL de Django: protección contra inyecciones de SQL.
Las funciones de ventana proporcionan una forma de aplicar funciones en particiones. A diferencia de las funciones de agregación normales que calculan un resultado final para cada conjunto definido por el grupo by, las funciones de ventana operan sobre marcos y particiones, y calculan el resultado para cada fila.
Puede especificar múltiples ventanas en la misma consulta lo que en Django ORM sería equivalente a incluir múltiples expresiones en un llamado a QuerySet.annotate(). El ORM no utiliza las ventanas nombradas, sino que forman parte de las columnas seleccionadas.
Defaults a %(expression)s OVER (%(window)s). Si solo se proporciona el argumento expression, la cláusula de ventana estará en blanco.
La clase Window es la expresión principal para una cláusula OVER.
El argumento expression es ya sea una función de ventana, una función de agregado o una expresión compatible en una cláusula de ventana.
El argumento partition_by acepta una expresión o una secuencia de expresiones (los nombres de columna deben estar envueltos en un objeto F) que controlan la particionado de las filas. La particionado reduce el conjunto de filas utilizadas para calcular el conjunto de resultados.
El campo de salida se especifica ya sea como argumento o por la expresión.
El argumento order_by acepta una expresión en la que puedes llamar a asc() y desc(), una cadena de un nombre de campo (con un prefijo opcional "-" que indica orden descendente), o una tupla o lista de cadenas y/o expresiones. El ordenamiento controla el orden en que se aplica la expresión. Por ejemplo, si sumas sobre las filas en una particionado, el primer resultado es el valor de la primera fila, el segundo es la suma de la primera y segunda fila.
El parámetro frame especifica qué otras filas deben utilizarse en el cálculo. Consulte ventanas para obtener más detalles.
Por ejemplo, para anotar cada película con la media de calificaciones para las películas del mismo estudio en el mismo género y año de lanzamiento:
>>> from django.db.models import Avg, F, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... ),
... )
Esto te permite comprobar si una película está calificada mejor o peor que sus pares.
Puede que desees aplicar múltiples expresiones sobre la misma ventana, es decir, la misma particionado y marco. Por ejemplo, podrías modificar el ejemplo anterior para incluir también las mejores y peores calificaciones en cada grupo de película (mismo estudio, género y año de lanzamiento) utilizando tres funciones de ventana en la misma consulta. La partición y ordenación del ejemplo anterior se extrae a un diccionario para reducir la repetición:
>>> from django.db.models import Avg, F, Max, Min, Window
>>> window = {
... "partition_by": [F("studio"), F("genre")],
... "order_by": "released__year",
... }
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... **window,
... ),
... best=Window(
... expression=Max("rating"),
... **window,
... ),
... worst=Window(
... expression=Min("rating"),
... **window,
... ),
... )
Filterado contra funciones de ventana se admite siempre que las consultas no sean disjuntas (no utilicen OR o XOR como conectores) y contra un conjunto de resultados que realice agregaciones.
Por ejemplo, una consulta que depende de la agregación y tiene un filtro con OR contra una función de ventana y un campo no está soportada. Aplicar predicados combinados después de la agregación podría hacer que las filas que normalmente se excluirían de los grupos se incluyan:
>>> qs = Movie.objects.annotate(
... category_rank=Window(Rank(), partition_by="category", order_by="-rating"),
... scenes_count=Count("actors"),
... ).filter(Q(category_rank__lte=3) | Q(title__contains="Batman"))
>>> list(qs)
NotImplementedError: Heterogeneous disjunctive predicates against window functions
are not implemented when performing conditional aggregation.
Entre los backends de bases de datos integrados de Django, MySQL, PostgreSQL y Oracle admiten expresiones de ventana. El soporte para diferentes características de expresiones de ventana varía entre los diferentes motores de base de datos. Por ejemplo, las opciones en asc() y desc() pueden no estar disponibles. Consulta la documentación de tu motor de base de datos según sea necesario.
Para un marco de ventana, puedes elegir entre una secuencia de filas basada en rango o una secuencia ordinaria de filas.
Esta atributo se establece en 'RANGE'.
PostgreSQL tiene un soporte limitado para ValueRange y solo admite el uso de los puntos finales estándar, como CURRENT ROW y UNBOUNDED FOLLOWING.
Se agregó la argumento exclusion.
Esta atributo se establece en 'ROWS'.
Se agregó la argumento exclusion.
Ambas clases devuelven SQL con el template:
%(frame_type)s BETWEEN %(start)s AND %(end)s
El exclusion argument permite excluir filas (CURRENT_ROW), grupos (GROUP) y empates (TIES) de las ventanas de marcos en bases de datos compatibles:
%(frame_type)s BETWEEN %(start)s AND %(end)s EXCLUDE %(exclusion)s
Los marcos estrechan las filas que se utilizan para calcular el resultado. Se desplazan desde algún punto de inicio hasta algún punto final especificado. Los marcos pueden usarse con y sin particiones, pero a menudo es una buena idea especificar un ordenamiento de la ventana para asegurar un resultado determinista. En un marco, un compañero en un marco es una fila con un valor equivalente, o todas las filas si no está presente una cláusula de ordenamiento.
El punto de inicio por defecto para un marco es UNBOUNDED PRECEDING que es la primera fila de la partición. El punto final siempre se incluye explícitamente en el SQL generado por el ORM y, por defecto, es UNBOUNDED FOLLOWING. El marco predeterminado incluye todas las filas desde la partición hasta la última fila del conjunto.
Los valores aceptados para los argumentos start y end son None, un entero o cero. Un entero negativo para start resulta en N PRECEDING, mientras que None produce UNBOUNDED PRECEDING. En modo ROWS, se puede utilizar un entero positivo para start lo que da como resultado N FOLLOWING. Los enteros positivos se aceptan para end y resultan en N FOLLOWING. En modo ROWS, se puede utilizar un entero negativo para end lo que da como resultado N PRECEDING. Para ambos start y end, cero devolverá CURRENT ROW.
Hay una diferencia en qué incluye CURRENT ROW. Cuando se especifica en modo ROWS, el marco comienza o termina con la fila actual. Cuando se especifica en modo RANGE, el marco comienza o termina en la primera o última compañera según la cláusula de ordenamiento. Por lo tanto, RANGE CURRENT ROW evalúa la expresión para las filas que tienen el mismo valor especificado por la cláusula de ordenamiento. Debido a que el template incluye ambos puntos de inicio y fin, esto puede expresarse con:
ValueRange(start=0, end=0)
Si los «compañeros» de una película se describen como películas estrenadas por la misma productora en el mismo género en el mismo año, este ejemplo RowRange anota cada película con la calificación promedio de dos películas anteriores y dos posteriores a ella:
>>> from django.db.models import Avg, F, RowRange, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... frame=RowRange(start=-2, end=2),
... ),
... )
Si la base de datos lo soporta, puedes especificar los puntos de inicio y fin basados en valores de una expresión en la partición. Si el campo released del modelo Movie almacena el mes de estreno de cada película, este ejemplo ValueRange anota cada película con la calificación promedio de sus compañeros estrenados entre doce meses antes y doce meses después de cada película:
>>> from django.db.models import Avg, F, ValueRange, Window
>>> Movie.objects.annotate(
... avg_rating=Window(
... expression=Avg("rating"),
... partition_by=[F("studio"), F("genre")],
... order_by="released__year",
... frame=ValueRange(start=-12, end=12),
... ),
... )
Se agregó soporte para enteros positivos start y enteros negativos end para RowRange.
A continuación, encontrarás detalles técnicos de implementación que pueden ser útiles a los autores de bibliotecas. La API técnica y los ejemplos a continuación ayudarán con la creación de expresiones de consulta genéricas que puedan extender la funcionalidad incorporada que proporciona Django.
Las expresiones de consulta implementan la API de expresión de consulta, pero también exponen un número de métodos y atributos adicionales listados a continuación. Todas las expresiones de consulta deben heredar de Expression() o una subclase relevante.
Cuando una expresión de consulta envuelve otra expresión, es responsable de llamar a los métodos adecuados en la expresión envuelta.
Indica a Django que esta expresión se puede utilizar en Field.db_default. Por defecto es False.
Indica a Django que esta expresión se puede utilizar durante una validación de restricciones. Las expresiones con constraint_validation_compatible establecidas en False deben tener solo una expresión fuente. Por defecto es True.
Indica a Django que esta expresión contiene un agregado y que se necesita agregar una cláusula GROUP BY a la consulta.
Indica a Django que esta expresión contiene una Window de expresión. Se utiliza, por ejemplo, para deshabilitar las expresiones de funciones de ventana en consultas que modifican datos.
Indica a Django que esta expresión se puede referenciar en QuerySet.filter(). Por defecto es True.
Indica a Django que esta expresión se puede utilizar como la expresión fuente en Window. Por defecto es False.
Indica a Django qué valor debe devolverse cuando la expresión se utiliza para aplicar una función sobre un conjunto de resultados vacío. Por defecto es NotImplemented que fuerza a la expresión a ser computada en la base de datos.
Los textos traducidos son:
Django debe saber que esta expresión permite expresiones compuestas, por ejemplo, para apoyar claves primarias compuestas <cpk-and-database-functions>. Por defecto es False.
Proporciona la oportunidad de realizar cualquier procesamiento o validación de la expresión antes de que se agregue a la consulta. resolve_expression() también debe llamarse en cualquier expresión anidada. Un copy() de self debería devolverse con las transformaciones necesarias.
query es la implementación de la consulta del backend.
allow_joins es un booleano que permite o deniega el uso de joins en la consulta.
reuse es un conjunto de joins reutilizables para escenarios de multi-join.
summarize es un booleano que, cuando True, indica que la consulta que se está computando es una consulta agregada terminal.
for_save es un booleano que, cuando True, indica que la consulta que se está ejecutando realiza una creación o actualización.
Devuelve una lista ordenada de expresiones internas. Por ejemplo:
>>> Sum(F("foo")).get_source_expressions()
[F('foo')]
Toma una lista de expresiones y las almacena de tal manera que get_source_expressions() pueda devolverlas.
Returns a clone (copia) de self, con cualquier alias de columna relabelizado. Los aliases de columnas se renombran cuando se crean subconsultas. relabeled_clone() también debe llamarse en cualquier expresión anidada y asignarse a la copia.
change_map es un diccionario que mapea los antiguos alias a nuevos alias.
Ejemplo:
def relabeled_clone(self, change_map):
clone = copy.copy(self)
clone.expression = self.expression.relabeled_clone(change_map)
return clone
Una función de llamada que permite coercer value a un tipo más apropiado.
expression es lo mismo que self.
Responsable de devolver la lista de columnas referenciadas por esta expresión. Se debe llamar a get_group_by_cols() en cualquier expresión anidada. Los objetos F(), en particular, guardan una referencia a una columna.
Devuelve la expresión lista para ser ordenada en orden ascendente.
nulls_first y nulls_last definen cómo se ordenan los valores nulos. Consulte Usando F() para ordenar valores nulos para ejemplo de uso.
Devuelve la expresión lista para ser ordenada en orden descendente.
nulls_first y nulls_last definen cómo se ordenan los valores nulos. Consulte Usando F() para ordenar valores nulos para ejemplo de uso.
Devuelve self con cualquier modificación requerida para revertir el orden de ordenación dentro de una llamada a order_by. Como ejemplo, una expresión que implemente NULLS LAST cambiaría su valor a ser NULLS FIRST. Las modificaciones solo se requieren para las expresiones que implementen el orden de orden como OrderBy. Este método se llama cuando reverse() se llama en un conjunto de consultas.
Puedes escribir tus propias clases de expresiones de consulta que utilicen y se integren con otras expresiones de consulta. Vamos a pasar por un ejemplo escribiendo una implementación de la función SQL COALESCE sin utilizar las expresiones Func() integradas.
La función SQL COALESCE está definida como tomando una lista de columnas o valores. Devolverá la primera columna o valor que no sea NULL.
Empezaremos definiendo el template a utilizar para la generación de SQL y un método __init__() para establecer algunas atributos:
from django.db.models import Expression
class Coalesce(Expression):
template = "COALESCE( %(expressions)s )"
def __init__(self, expressions, output_field):
super().__init__(output_field=output_field)
if len(expressions) < 2:
raise ValueError("expressions must have at least 2 elements")
for expression in expressions:
if not hasattr(expression, "resolve_expression"):
raise TypeError("%r is not an Expression" % expression)
self.expressions = expressions
Realizamos una validación básica en los parámetros, incluyendo requerir al menos 2 columnas o valores, y asegurándonos de que sean expresiones. Estamos requiriendo output_field aquí para que Django sepa qué tipo de campo de modelo asignar al resultado eventual.
Ahora implementaremos la preprocesamiento y validación. Dado que en este punto no tenemos ninguna validación propia, delegaremos a las expresiones anidadas:
def resolve_expression(
self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False
):
c = self.copy()
c.is_summary = summarize
for pos, expression in enumerate(self.expressions):
c.expressions[pos] = expression.resolve_expression(
query, allow_joins, reuse, summarize, for_save
)
return c
A continuación, escribiremos el método responsable de generar el SQL:
def as_sql(self, compiler, connection, template=None):
sql_expressions, sql_params = [], []
for expression in self.expressions:
sql, params = compiler.compile(expression)
sql_expressions.append(sql)
sql_params.extend(params)
template = template or self.template
data = {"expressions": ",".join(sql_expressions)}
return template % data, sql_params
def as_oracle(self, compiler, connection):
"""
Example of vendor specific handling (Oracle in this case).
Let's make the function name lowercase.
"""
return self.as_sql(compiler, connection, template="coalesce( %(expressions)s )")
Los métodos as_sql() pueden admitir argumentos de palabra clave personalizados, lo que permite a los métodos as_vendorname() sobrescribir datos utilizados para generar la cadena de SQL. Utilizar argumentos de palabra clave en as_sql() para la personalización es preferible a mutar self dentro de los métodos as_vendorname() ya que el último puede provocar errores al ejecutarse en diferentes backends de bases de datos. Si tu clase depende de atributos de clase para definir datos, considera permitir sobrescripciones en tu método as_sql().
Generamos el SQL para cada una de las expressions utilizando el método compiler.compile(), y unimos los resultados entre sí con comas. Luego se rellena el template con nuestros datos, y se devuelven el SQL y los parámetros.
También hemos definido una implementación personalizada específica para el backend Oracle. La función as_oracle() se llamará en lugar de as_sql() si se está utilizando el backend Oracle.
Finalmente, implementaremos los métodos restantes que permitan a nuestra expresión de consulta jugar bien con otras expresiones de consulta:
def get_source_expressions(self):
return self.expressions
def set_source_expressions(self, expressions):
self.expressions = expressions
Vamos a ver cómo funciona:
>>> from django.db.models import F, Value, CharField
>>> qs = Company.objects.annotate(
... tagline=Coalesce(
... [F("motto"), F("ticker_name"), F("description"), Value("No Tagline")],
... output_field=CharField(),
... )
... )
>>> for c in qs:
... print("%s: %s" % (c.name, c.tagline))
...
Google: Do No Evil
Apple: AAPL
Yahoo: Internet Company
Django Software Foundation: No Tagline
Dado que los argumentos de palabra clave de una Func para __init__() (**extra) y as_sql() (**extra_context) se interpolan en la cadena SQL en lugar de pasarlos como parámetros de consulta (donde el controlador de base de datos escaparía), no deben contener entrada de usuario no confiable.
Por ejemplo, si substring es proporcionado por el usuario, esta función es vulnerable a la inyección SQL:
from django.db.models import Func
class Position(Func):
function = "POSITION"
template = "%(function)s('%(substring)s' in %(expressions)s)"
def __init__(self, expression, substring):
# substring=substring is an SQL injection vulnerability!
super().__init__(expression, substring=substring)
Esta función genera una cadena SQL sin parámetros. Dado que substring se pasa a super().__init__() como argumento de palabra clave, se interpola en la cadena SQL antes de enviar la consulta a la base de datos.
Aquí tienes una reescritura corregida:
class Position(Func):
function = "POSITION"
arg_joiner = " IN "
def __init__(self, expression, substring):
super().__init__(substring, expression)
Con substring pasada en lugar como un argumento posicional, se pasaría como parámetro en la consulta de la base de datos.
Si estás utilizando un backend de base de datos que utiliza una sintaxis SQL diferente para cierta función, puedes agregar soporte para ella mediante el monkey patching de una nueva método sobre la clase de la función.
Digamos que estamos escribiendo un backend para Microsoft’s SQL Server que utiliza la SQL LEN en lugar de LENGTH para la función Length. Vamos a aplicar un monkey patch con un nuevo método llamado as_sqlserver() sobre la clase Length:
from django.db.models.functions import Length
def sqlserver_length(self, compiler, connection):
return self.as_sql(compiler, connection, function="LEN")
Length.as_sqlserver = sqlserver_length
Puedes personalizar el SQL utilizando el parámetro template de as_sql().
Utilizamos as_sqlserver() porque django.db.connection.vendor devuelve sqlserver para la parte trasera.
Los backends de terceros pueden registrar sus funciones en el archivo __init__.py a nivel superior del paquete del backend o en un archivo expressions.py (o paquete) a nivel superior que se importa desde el archivo __init__.py a nivel superior.
Para proyectos de usuarios que deseen parchear la parte trasera que están utilizando, este código debe vivir en un método AppConfig.ready().
may 31, 2026