Considere modelos simples de Django Event
y Participant
:
class Event(models.Model):
title = models.CharField(max_length=100)
class Participant(models.Model):
event = models.ForeignKey(Event, db_index=True)
is_paid = models.BooleanField(default=False, db_index=True)
Es fácil anotar consultas de eventos con el número total de participantes:
events = Event.objects.all().annotate(participants=models.Count('participant'))
¿Cómo anotar con el recuento de participantes filtrados por is_paid=True
?
Necesito consultar todos los eventos independientemente del número de participantes, por ejemplo, no necesito filtrar por resultado anotado. Si hay 0
participantes, está bien, solo necesito0
valor anotado.
El ejemplo de la documentación no funciona aquí, porque excluye objetos de la consulta en lugar de anotarlos con 0
.
Actualizar. Django 1.8 tiene una nueva función de expresiones condicionales , por lo que ahora podemos hacer lo siguiente:
events = Event.objects.all().annotate(paid_participants=models.Sum(
models.Case(
models.When(participant__is_paid=True, then=1),
default=0,
output_field=models.IntegerField()
)))
Actualización 2. Django 2.0 tiene una nueva función de agregación condicional , consulte la respuesta aceptada a continuación.
aggregate
se muestra el uso. ¿Ya ha probado este tipo de consultas? (¡No lo he hecho y quiero creer! :)Acabo de descubrir que Django 1.8 tiene una nueva función de expresiones condicionales , así que ahora podemos hacer lo siguiente:
fuente
Count
(en lugar deSum
), supongo que deberíamos establecerdefault=None
(si no usamos elfilter
argumento django 2 ).ACTUALIZAR
El enfoque de subconsulta que menciono ahora es compatible con Django 1.11 a través de subconsultas-expresiones .
Prefiero esto a la agregación (suma + caso) , porque debería ser más rápido y fácil de optimizar (con la indexación adecuada) .
Para la versión anterior, se puede lograr lo mismo usando
.extra
fuente
.extra
, ya que prefiero evitar SQL en Django :) Actualizaré la pregunta.Django 1.8.2
, así que supongo que estás con esa versión y es por eso que funciona para ti. Puede leer más sobre eso aquí y aquíNone
también. Mi solución fue usarCoalesce
(from django.db.models.functions import Coalesce
). Se utiliza de esta manera:Coalesce(Subquery(...), 0)
. Sin embargo, puede haber un mejor enfoque.Sugeriría utilizar el
.values
método de suParticipant
consultas en lugar.En resumen, lo que quieres hacer viene dado por:
Un ejemplo completo es el siguiente:
Crear 2
Event
s:Agregue
Participant
s a ellos:Agrupe todos
Participant
los correos electrónicos por suevent
campo:Aquí se necesita distinto:
Lo que
.values
y.distinct
están haciendo aquí es que están creando dos cubos deParticipant
s agrupados por su elementoevent
. Tenga en cuenta que esos cubos contienenParticipant
.Luego puede anotar esos depósitos, ya que contienen el conjunto de originales
Participant
. Aquí queremos contar el número deParticipant
, esto se hace simplemente contando losid
s de los elementos en esos cubos (ya que sonParticipant
):Finalmente, solo desea
Participant
con unis_paid
serTrue
, puede agregar un filtro delante de la expresión anterior, y esto produce la expresión que se muestra arriba:El único inconveniente es que debe recuperar el
Event
después, ya que solo tiene elid
del método anterior.fuente
Qué resultado estoy buscando:
En general, tendría que usar dos consultas diferentes:
Pero quiero ambos en una consulta. Por lo tanto:
Resultado:
fuente