Rozważ proste modele Django Event
i 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)
W zapytaniu dotyczącym wydarzeń można łatwo opisać całkowitą liczbę uczestników:
events = Event.objects.all().annotate(participants=models.Count('participant'))
Jak dodawać adnotacje z liczbą przefiltrowanych uczestników is_paid=True
?
Muszę odpytywać wszystkie zdarzenia niezależnie od liczby uczestników, np. Nie muszę filtrować według wyników z adnotacjami. Jeśli są 0
uczestnicy, w porządku, potrzebuję tylko 0
wartości z adnotacjami.
Przykład z dokumentacji nie działa tutaj, ponieważ wyklucza obiektów z kwerendy zamiast opisywania ich 0
.
Aktualizacja. Django 1.8 ma nową funkcję wyrażeń warunkowych , więc teraz możemy zrobić tak:
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()
)))
Aktualizacja 2. Django 2.0 ma nową funkcję agregacji warunkowej , zobacz zaakceptowaną odpowiedź poniżej.
aggregate
pokazane jest tylko użycie. Czy przetestowałeś już takie zapytania? (Nie mam i chcę wierzyć! :)Właśnie odkryłem, że Django 1.8 ma nową funkcję wyrażeń warunkowych , więc teraz możemy zrobić tak:
źródło
Count
(zamiastSum
), myślę, że powinniśmy ustawićdefault=None
(jeśli nie używamyfilter
argumentu django 2 ).AKTUALIZACJA
Podejście pod-zapytań, o którym wspominałem, jest teraz obsługiwane w Django 1.11 za pośrednictwem wyrażeń podzapytań .
Wolę to od agregacji (suma + przypadek) , ponieważ powinno być szybsze i łatwiejsze do optymalizacji (przy odpowiednim indeksowaniu) .
W przypadku starszej wersji to samo można osiągnąć za pomocą
.extra
źródło
.extra
, ponieważ wolę unikać SQL w Django :) Zaktualizuję pytanie.Django 1.8.2
, więc myślę, że jesteś z tą wersją i dlatego działa dla ciebie. Możesz przeczytać więcej na ten temat tutaj i tutajNone
też. Moim rozwiązaniem było użycieCoalesce
(from django.db.models.functions import Coalesce
). Go używać tak:Coalesce(Subquery(...), 0)
. Jednak może być lepsze podejście.Sugerowałbym zamiast tego użycie
.values
metody twojegoParticipant
zestawu zapytań.Krótko mówiąc, to, co chcesz zrobić, to:
Kompletny przykład jest następujący:
Utwórz 2
Event
s:Dodaj
Participant
do nich s:Grupuj wszystkie
Participant
według ichevent
pól:Tutaj potrzebny jest odrębny:
Co
.values
i.distinct
robią tutaj, to to, że tworzą dwa segmentyParticipant
s pogrupowane według ich elementuevent
. Zauważ, że te zasobniki zawierająParticipant
.Następnie możesz dodać adnotacje do tych zasobników, ponieważ zawierają one zestaw oryginałów
Participant
. Tutaj chcemy policzyć liczbęParticipant
, robimy to po prostu przez policzenieid
s elementów w tych zasobnikach (ponieważ sąParticipant
):Ostatecznie chcesz tylko
Participant
zis_paid
istotąTrue
, możesz po prostu dodać filtr przed poprzednim wyrażeniem, a to daje wyrażenie pokazane powyżej:Jedyną wadą jest to, że musisz odzyskać
Event
później, ponieważ masz tylkoid
z powyższej metody.źródło
Jakiego wyniku szukam:
Generalnie musiałbym użyć dwóch różnych zapytań:
Ale chcę, aby oba w jednym zapytaniu. W związku z tym:
Wynik:
źródło