Рассмотрим простые модели Django Event
и 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)
Аннотировать запрос событий легко, указав общее количество участников:
events = Event.objects.all().annotate(participants=models.Count('participant'))
Как добавить аннотацию с подсчетом отфильтрованных участников is_paid=True
?
Мне нужно запрашивать все события независимо от количества участников, например, мне не нужно фильтровать по аннотированному результату. Если есть 0
участники, ничего страшного, мне просто нужно 0
аннотированное значение.
Пример из документации здесь не работает, так как он исключает объекты из запроса вместо аннотирования их 0
.
Обновить. В Django 1.8 появилась новая функция условных выражений , поэтому теперь мы можем делать это так:
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()
)))
Обновление 2. В Django 2.0 появилась новая функция условного агрегирования , см. Принятый ответ ниже.
aggregate
показано только использование. Вы уже тестировали такие запросы? (Не было и хочу верить! :)Только что обнаружил, что в Django 1.8 есть новая функция условных выражений , так что теперь мы можем делать вот так:
источник
Count
(вместоSum
) я думаю, мы должны установитьdefault=None
(если не использоватьfilter
аргумент django 2 ).ОБНОВИТЬ
Подход подзапроса, о котором я упоминал, теперь поддерживается в Django 1.11 через выражения подзапроса .
Я предпочитаю это агрегации (сумма + регистр) , потому что это должно быть быстрее и проще для оптимизации (с правильной индексацией) .
Для более старой версии то же самое можно сделать, используя
.extra
источник
.extra
, так как предпочитаю избегать SQL в Django :) Я обновлю вопрос.Django 1.8.2
, поэтому я думаю, что вы используете эту версию, и поэтому она работает на вас. Вы можете узнать больше об этом здесь и здесьNone
тоже получил . Мое решение заключалось в использованииCoalesce
(from django.db.models.functions import Coalesce
). Вы можете использовать его как это:Coalesce(Subquery(...), 0)
. Хотя может быть и лучший подход.Я бы предложил вместо этого использовать
.values
метод вашегоParticipant
запроса.Короче говоря, то, что вы хотите сделать, дает:
Полный пример выглядит следующим образом:
Создайте 2
Event
с:Добавьте
Participant
к ним:Сгруппируйте все
Participant
по ихevent
полю:Здесь нужно внятное:
Что
.values
и.distinct
здесь делается, так это то, что они создают два сегментаParticipant
s, сгруппированных по их элементамevent
. Обратите внимание, что эти корзины содержат файлыParticipant
.Затем вы можете аннотировать эти сегменты, поскольку они содержат набор оригиналов
Participant
. Здесь мы хотим подсчитать количествоParticipant
, это просто делается путем подсчетаid
s элементов в этих сегментах (поскольку они естьParticipant
):Наконец, вы хотите только
Participant
сis_paid
существомTrue
, вы можете просто добавить фильтр перед предыдущим выражением, и это даст выражение, показанное выше:Единственный недостаток заключается в том, что вам нужно получить
Event
потом, так как у вас есть толькоid
метод, описанный выше.источник
Какой результат я ищу:
В общем, мне пришлось бы использовать два разных запроса:
Но мне нужны оба в одном запросе. Следовательно:
Результат:
источник