Аннотирование набора запросов с использованием агрегации значений с более чем одним полем
Аннотации в Django действительно замечательны. Однако я не могу понять, как быть с аннотациями, где требуется несколько values()
.
Вопрос:
Я хотел бы аннотировать author_queryset
с подсчетами элементов в связанном m2m. Я не знаю, нужно ли мне использовать Subquery
или нет, однако:
annotated_queryset = author_queryset.annotate(genre_counts=Subquery(genre_counts))
Возвращается:
SyntaxError: subquery must return only one column
Я пробовал приводить значения к JSONField
, чтобы вернуть их в одно поле, надеясь, что смогу использовать JSONBagg
на нем, поскольку я использую postgres и должен фильтровать результат. Но, похоже, это не работает. Здесь есть отличная информация о фильтрации на них. Также скоро появится кое-что для postgres в версии для разработчиков под названием ArraySubQuery()
, которое, похоже, поможет решить эту проблему. Однако я не могу использовать эту возможность, пока она не появится в стабильном релизе.
Желаемый результат
Я хотел бы аннотировать, чтобы я мог фильтровать на основе аннотаций, например, так:
annotated_queryset.filter(genre_counts__scifi__gte=5)
Деталь
Я могу использовать dunders, чтобы получить связанное поле, а затем подсчитать так:
# get all the authors with Virginia in their name
author_queryset = Author.objects.filter(name__icontains='Virginia')
author_queryset.count()
# returns: 21
# aggregate the book counts by genre in the Book m2m model
genre_counts = author_queryset.values('id','main_books__genre').annotate(genre_counts=Count('main_books__genre'))
genre_counts.count()
# returns: 25
это связано с тем, что для каждого объекта Author в наборе запросов может быть возвращено несколько подсчетов жанров. В данном конкретном примере есть Автор, у которого есть книги в 4 различных жанрах:
Для примера:
...
{'id': 'authorid:0054f04', 'main_books__genre': 'scifi', 'genre_counts': 1}
{'id': 'authorid:c245457', 'main_books__genre': 'fantasy', 'genre_counts': 4}
{'id': 'authorid:a129a73', 'main_books__genre': None, 'genre_counts': 0}
{'id': 'authorid:f41f14b', 'main_books__genre': 'mystery', 'genre_counts': 16}
{'id': 'authorid:f41f14b', 'main_books__genre': 'romance', 'genre_counts': 1}
{'id': 'authorid:f41f14b', 'main_books__genre': 'scifi', 'genre_counts': 9}
{'id': 'authorid:f41f14b', 'main_books__genre': 'fantasy', 'genre_counts': 3}
...
и есть еще один Автор с 2, остальные имеют по одному жанру. Что составляет 25 значений.
Надеюсь, это имеет смысл для кого-то! Я уверен, что есть способ сделать это правильно, не дожидаясь появления функции, описанной выше.
Вы хотите использовать .annotate(
без Subquery
, потому что, как вы обнаружили, это должно возвращать единственное значение. Вы должны быть в состоянии охватить все отношения в выражении count первого аннотата
К сожалению, в настоящее время Django не поддерживает то, что вы ищете с помощью genre_counts__scifi_gt=5
. Вы можете структурировать его таким образом, чтобы выполнять Count с переданным ему фильтром.
selected_genre = 'scifi'
annotated_queryset = author_queryset.annotate(
genre_count=Count("main_books__genre", filter=Q(genre=selected_genre))
).filter(genre_count__gte=5)
Чтобы получить полную разбивку, вам лучше вернуть разбивку и сделать окончательную агрегацию в приложении, как вы показали в своем вопросе.