Ответ
Сложные QuerySet в Django строятся с помощью комбинации методов для агрегации, аннотации и выполнения подзапросов, что позволяет получить нужные данные за один запрос к базе данных, избегая проблемы N+1.
Пример: Получим все посты, аннотируем их количеством активных комментариев и датой последнего активного комментария. Также сразу подгрузим связанные данные об авторе и отфильтрованные комментарии.
from django.db.models import Count, Q, Subquery, OuterRef, Prefetch
from blog.models import Post, Comment
# Подзапрос для получения даты последнего активного комментария
last_comment_subquery = Comment.objects.filter(
post=OuterRef('pk'),
is_active=True
).order_by('-created_at').values('created_at')[:1]
# Основной запрос
posts = Post.objects.annotate(
# 1. Аннотация: добавляем вычисляемое поле с количеством
active_comments_count=Count('comments', filter=Q(comments__is_active=True)),
# 2. Аннотация с подзапросом
last_comment_date=Subquery(last_comment_subquery)
).select_related('author') # 3. Оптимизация: JOIN для получения автора
.prefetch_related(
# 4. Оптимизация: отдельный запрос для получения только активных комментариев
Prefetch('comments', queryset=Comment.objects.filter(is_active=True), to_attr='active_comments')
).order_by('-last_comment_date')
Ключевые компоненты этого запроса:
annotate()иCount(): Добавляют к каждому объектуPostвычисляемое полеactive_comments_count.SubqueryиOuterRef: Выполняют вложенный запрос для каждого поста, чтобы найти дату последнего комментария.select_related('author'): Избегает дополнительных запросов к БД для получения данных автора (используетJOIN).prefetch_related()сPrefetch: Эффективно загружает связанные активные комментарии в отдельном запросе, решая проблему N+1.