update select_related example python django django-models django-aggregation

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:

  1. Crear 2 Event s:

    event1 = Event.objects.create(title=''event1'') event2 = Event.objects.create(title=''event2'')

  2. 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)]

  3. Agrupe a todos los Participant por su campo de event :

    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 de Participant agrupados por su event elemento. Tenga en cuenta que esos cubos contienen Participant .

  4. Luego puede anotar esos depósitos ya que contienen el conjunto de Participant original. Aquí queremos contar el número de Participant , esto simplemente se hace contando los id s de los elementos en esos cubos (ya que son Participant ):

    Participant.objects/ .values(''event'')/ .distinct()/ .annotate(models.Count(''id'')) > <QuerySet [{''event'': 1, ''id__count'': 10}, {''event'': 2, ''id__count'': 50}]>

  5. Finalmente, solo desea un Participant con un is_paid siendo True , 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}]>