Considérez les modèles Django simples Event
et 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)
Il est facile d'annoter la requête d'événements avec le nombre total de participants:
events = Event.objects.all().annotate(participants=models.Count('participant'))
Comment annoter avec le nombre de participants filtrés par is_paid=True
?
J'ai besoin d'interroger tous les événements quel que soit le nombre de participants, par exemple je n'ai pas besoin de filtrer par résultat annoté. S'il y a des 0
participants, ça va, j'ai juste besoin 0
d'une valeur annotée.
L' exemple de la documentation ne fonctionne pas ici, car il exclut les objets de la requête au lieu de les annoter avec 0
.
Mettre à jour. Django 1.8 a une nouvelle fonctionnalité d'expressions conditionnelles , donc maintenant nous pouvons faire comme ceci:
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()
)))
Mise à jour 2. Django 2.0 a une nouvelle fonctionnalité d' agrégation conditionnelle , voir la réponse acceptée ci-dessous.
aggregate
utilisation est indiquée. Avez-vous déjà testé de telles requêtes? (Je n'ai pas et je veux croire! :)Je viens de découvrir que Django 1.8 a une nouvelle fonctionnalité d'expressions conditionnelles , donc maintenant nous pouvons faire comme ceci:
la source
Count
(au lieu deSum
), je suppose que nous devrions définirdefault=None
(si vous n'utilisez pas l'filter
argument django 2 ).METTRE À JOUR
L'approche de sous-requête que je mentionne est désormais prise en charge dans Django 1.11 via des expressions de sous-requête .
Je préfère cela à l'agrégation (somme + cas) , car il devrait être plus rapide et plus facile à optimiser (avec une indexation appropriée) .
Pour les versions plus anciennes, la même chose peut être obtenue en utilisant
.extra
la source
.extra
, car je préfère éviter SQL dans Django :) Je vais mettre à jour la question.Django 1.8.2
, donc je suppose que vous êtes avec cette version et c'est pourquoi cela fonctionne pour vous. Vous pouvez en savoir plus à ce sujet ici et iciNone
aussi. Ma solution était d'utiliserCoalesce
(from django.db.models.functions import Coalesce
). Vous pouvez l' utiliser comme ceci:Coalesce(Subquery(...), 0)
. Il y a peut-être une meilleure approche, cependant.Je suggérerais d'utiliser la
.values
méthode de votre jeu deParticipant
requêtes à la place.Pour faire court, ce que vous voulez faire est donné par:
Un exemple complet est le suivant:
Créer 2
Event
s:Ajoutez-y des
Participant
s:Regroupez tous les
Participant
s par leurevent
domaine:Ici, distinct est nécessaire:
Ce qu'ils font
.values
et.distinct
font ici, c'est qu'ils créent deux seaux deParticipant
s regroupés par leur élémentevent
. Notez que ces buckets contiennentParticipant
.Vous pouvez ensuite annoter ces compartiments car ils contiennent l'ensemble de l'original
Participant
. Ici, nous voulons compter le nombre deParticipant
, cela se fait simplement en comptant lesid
s des éléments dans ces buckets (puisque ceux-ci sontParticipant
):Enfin, vous ne voulez
Participant
qu'avec unis_paid
êtreTrue
, vous pouvez simplement ajouter un filtre devant l'expression précédente, et cela donne l'expression ci-dessus:Le seul inconvénient est que vous devez récupérer la
Event
suite car vous ne disposez queid
de la méthode ci-dessus.la source
Quel résultat je recherche:
En général, je devrais utiliser deux requêtes différentes:
Mais je veux les deux dans une seule requête. Par conséquent:
Résultat:
la source