Considere modelos simples do Django Event
e 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)
É fácil anotar a consulta de eventos com o número total de participantes:
events = Event.objects.all().annotate(participants=models.Count('participant'))
Como fazer anotações com a contagem de participantes filtrada por is_paid=True
?
Preciso consultar todos os eventos, independentemente do número de participantes, por exemplo, não preciso filtrar por resultado anotado. Se houver 0
participantes, tudo bem, eu só preciso de um 0
valor anotado.
O exemplo da documentação não funciona aqui, porque exclui objetos da consulta em vez de anotá-los 0
.
Atualizar. O Django 1.8 possui um novo recurso de expressões condicionais , então agora podemos fazer o seguinte:
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()
)))
Atualização 2. O Django 2.0 possui o novo recurso de agregação condicional , veja a resposta aceita abaixo.
fonte
aggregate
uso é mostrado. Você já testou essas consultas? (Eu não tenho e quero acreditar!)Acabei de descobrir que o Django 1.8 possui um novo recurso de expressões condicionais , agora podemos fazer o seguinte:
fonte
Count
(em vez deSum
), acho que devemos definirdefault=None
(se não estiver usando ofilter
argumento django 2 ).ATUALIZAR
A abordagem de subconsulta que eu mencionei agora é suportada no Django 1.11 através de subconsulta-expressões .
Eu prefiro isso à agregação (soma + caso) , porque deve ser mais rápido e fácil ser otimizado (com indexação adequada) .
Para versões mais antigas, o mesmo pode ser alcançado usando
.extra
fonte
.extra
, como eu prefiro evitar SQL no Django :) Vou atualizar a pergunta.Django 1.8.2
, então acho que você está com essa versão e é por isso que está trabalhando para você. Você pode ler mais sobre isso aqui e aquiNone
também. Minha solução foi usarCoalesce
(from django.db.models.functions import Coalesce
). Você usá-lo como este:Coalesce(Subquery(...), 0)
. Pode haver uma abordagem melhor, no entanto.Eu sugeriria usar o
.values
método do seu conjunto deParticipant
consultas.Para resumir, o que você quer fazer é dado por:
Um exemplo completo é o seguinte:
Crie 2
Event
s:Adicione
Participant
s a eles:Agrupe todos os
Participant
s por seuevent
campo:Aqui é necessário distinto:
O que
.values
e.distinct
estão fazendo aqui é que eles estão criando dois baldes deParticipant
s agrupadas por seu elementoevent
. Observe que esses baldes contêmParticipant
.Em seguida, você pode fazer anotações nesses baldes, pois eles contêm o conjunto de originais
Participant
. Aqui queremos contar o número deParticipant
, isso é simplesmente feito contando osid
s dos elementos nesses buckets (já que sãoParticipant
):Finalmente, você quer apenas
Participant
umis_paid
serTrue
, basta adicionar um filtro na frente da expressão anterior, e isso gera a expressão mostrada acima:A única desvantagem é que você deve recuperar o valor
Event
posterior, pois possui apenasid
o método acima.fonte
Que resultado estou procurando:
Em geral, eu precisaria usar duas consultas diferentes:
Mas eu quero os dois em uma consulta. Conseqüentemente:
Resultado:
fonte