Аннотирование набора запросов с использованием агрегации значений с более чем одним полем

Аннотации в 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)

Чтобы получить полную разбивку, вам лучше вернуть разбивку и сделать окончательную агрегацию в приложении, как вы показали в своем вопросе.

Вернуться на верх