python - update - select_related django example
¿Cómo filtrar objetos para la anotación de recuento en Django? (5)
Acabo de descubrir que Django 1.8 tiene una nueva función de expresiones condicionales , así que ahora podemos hacer esto:
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()
)))
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 la consulta 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 necesito
0
en valor anotado.
El
ejemplo de la documentación
no funciona aquí, porque excluye los 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 esto:
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.
Sugeriría utilizar el método
.values
de su conjunto de consultas
Participant
su lugar.
Para abreviar, lo que quieres hacer viene dado por:
Participant.objects/
.filter(is_paid=True)/
.values(''event'')/
.distinct()/
.annotate(models.Count(''id''))
Un ejemplo completo es el siguiente:
-
Crear 2
Event
s:event1 = Event.objects.create(title=''event1'') event2 = Event.objects.create(title=''event2'')
-
Agregue
Participant
a ellos:part1l = [Participant.objects.create(event=event1, is_paid=((_%2) == 0))/ for _ in range(10)] part2l = [Participant.objects.create(event=event2, is_paid=((_%2) == 0))/ for _ in range(50)]
-
Agrupe a todos los
Participant
por su campo deevent
:Participant.objects.values(''event'') > <QuerySet [{''event'': 1}, {''event'': 1}, {''event'': 1}, {''event'': 1}, {''event'': 1}, {''event'': 1}, {''event'': 1}, {''event'': 1}, {''event'': 1}, {''event'': 1}, {''event'': 2}, {''event'': 2}, {''event'': 2}, {''event'': 2}, {''event'': 2}, {''event'': 2}, {''event'': 2}, {''event'': 2}, {''event'': 2}, {''event'': 2}, ''...(remaining elements truncated)...'']>
Aquí se necesita distinto:
Participant.objects.values(''event'').distinct() > <QuerySet [{''event'': 1}, {''event'': 2}]>
Lo que están haciendo
.values
y.distinct
aquí es que están creando dos cubos deParticipant
agrupados por suevent
elemento. Tenga en cuenta que esos cubos contienenParticipant
. -
Luego puede anotar esos depósitos ya que contienen el conjunto de
Participant
original. Aquí queremos contar el número deParticipant
, esto simplemente se hace contando losid
s de los elementos en esos cubos (ya que sonParticipant
):Participant.objects/ .values(''event'')/ .distinct()/ .annotate(models.Count(''id'')) > <QuerySet [{''event'': 1, ''id__count'': 10}, {''event'': 2, ''id__count'': 50}]>
-
Finalmente, solo desea un
Participant
con unis_paid
siendoTrue
, puede agregar un filtro delante de la expresión anterior, y esto produce la expresión que se muestra arriba:Participant.objects/ .filter(is_paid=True)/ .values(''event'')/ .distinct()/ .annotate(models.Count(''id'')) > <QuerySet [{''event'': 1, ''id__count'': 5}, {''event'': 2, ''id__count'': 25}]>
El único inconveniente es que debe recuperar el
Event
después, ya que solo tiene la
id
del método anterior.
La agregación condicional
en Django 2.0 le permite reducir aún más la cantidad de faff que esto ha sido en el pasado.
Esto también usará la lógica de
filter
Postgres, que es algo más rápido que un caso de suma (he visto números como 20-30% en bandas).
De todos modos, en su caso, estamos viendo algo tan simple como:
from django.db.models import Q, Count
events = Event.objects.annotate(
paid_participants=Count(''participants'', filter=Q(participants__is_paid=True))
)
Hay una sección separada en los documentos sobre el filtrado de anotaciones . Es lo mismo que la agregación condicional, pero más como mi ejemplo anterior. De cualquier manera, esto es mucho más saludable que las retorcidas subconsultas que estaba haciendo antes.
ACTUALIZAR
El enfoque de subquery-expressions que menciono ahora es compatible con Django 1.11 a través subquery-expressions .
Event.objects.annotate(
num_paid_participants=Subquery(
Participant.objects.filter(
is_paid=True,
event=OuterRef(''pk'')
).values(''event'')
.annotate(cnt=Count(''pk''))
.values(''cnt''),
output_field=models.IntegerField()
)
)
Prefiero esto sobre la agregación (suma + caso) , porque debería ser más rápido y más fácil de optimizar (con una indexación adecuada) .
Para versiones anteriores, se puede lograr lo mismo usando
.extra
Event.objects.extra(select={''num_paid_participants'': "/
SELECT COUNT(*) /
FROM `myapp_participant` /
WHERE `myapp_participant`.`is_paid` = 1 AND /
`myapp_participant`.`event_id` = `myapp_event`.`id`"
})
Qué resultado estoy buscando:
- Personas (cesionario) que tienen tareas agregadas a un informe. - Recuento único total de personas
- Las personas que tienen tareas agregadas a un informe pero, para la tarea, la capacidad de facturación es más de 0 solamente.
En general, tendría que usar dos consultas diferentes:
Task.objects.filter(billable_efforts__gt=0)
Task.objects.all()
Pero quiero ambos en una consulta. Por lo tanto:
Task.objects.values(''report__title'').annotate(withMoreThanZero=Count(''assignee'', distinct=True, filter=Q(billable_efforts__gt=0))).annotate(totalUniqueAssignee=Count(''assignee'', distinct=True))
Resultado:
<QuerySet [{''report__title'': ''TestReport'', ''withMoreThanZero'': 37, ''totalUniqueAssignee'': 50}, {''report__title'': ''Utilization_Report_April_2019'', ''withMoreThanZero'': 37, ''totalUniqueAssignee'': 50}]>