← Назад к вопросам

Делал ли subquery в Django

2.0 Middle🔥 171 комментариев
#Django#Python Core#Базы данных (SQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Subquery в Django ORM

Делал subquery в Django много раз при разработке сложных аналитических и фильтрующих запросов. Рассмотрю различные подходы и реальные примеры.

1. Простой Subquery для фильтрации

Найти всех пользователей, которые оставили комментарий после определённой даты:

from django.db.models import OuterRef, Subquery, Exists
from django.utils import timezone
from datetime import timedelta

# Способ 1: с использованием Subquery
recent_comments = Comment.objects.filter(
    user_id=OuterRef('pk'),
    created_at__gte=timezone.now() - timedelta(days=7)
).values('id')

users_with_recent_comments = User.objects.annotate(
    has_comment=Exists(recent_comments)
).filter(has_comment=True)

2. Subquery для получения значений

Получить последний комментарий каждого пользователя:

from django.db.models import Max, Subquery, OuterRef

# Получить дату последнего комментария
last_comment_date = Comment.objects.filter(
    user_id=OuterRef('pk')
).order_by('-created_at').values('created_at')[:1]

users = User.objects.annotate(
    last_comment=Subquery(last_comment_date)
).values('id', 'username', 'last_comment')

3. Exists для проверки существования записей

Найти пользователей с хотя бы одним активным постом:

from django.db.models import Exists, OuterRef, Q

active_posts = Post.objects.filter(
    author_id=OuterRef('pk'),
    is_published=True,
    status='active'
).values('id')

users_with_posts = User.objects.filter(
    Exists(active_posts)
)

4. Nested Subquery — многоуровневые запросы

Найти категории, которые имеют посты с комментариями:

from django.db.models import Exists, OuterRef, Q

# Слой 3: посты с комментариями
posts_with_comments = Comment.objects.filter(
    post_id=OuterRef('pk')
).values('id')

# Слой 2: категории с нужными постами
categories = Category.objects.filter(
    Exists(
        Post.objects.filter(
            category_id=OuterRef('pk'),
            Exists(posts_with_comments)
        ).values('id')
    )
)

5. Subquery с aggrегацией

Получить среднее количество комментариев на пост:

from django.db.models import Avg, Count, Subquery, F

# Для каждого пользователя: среднее комментариев на пост
comments_per_post = Comment.objects.filter(
    post__author_id=OuterRef('pk')
).values('post__author_id').annotate(
    avg_comments=Avg('id')
).values('avg_comments')

users = User.objects.annotate(
    avg_comments=Subquery(comments_per_post)
).values('id', 'username', 'avg_comments')

6. Case/When с Subquery

Получить статус активности пользователя:

from django.db.models import Case, When, Value, CharField, Exists, OuterRef
from django.utils import timezone
from datetime import timedelta

recent_activity = Activity.objects.filter(
    user_id=OuterRef('pk'),
    timestamp__gte=timezone.now() - timedelta(hours=24)
).values('id')

users = User.objects.annotate(
    activity_status=Case(
        When(Exists(recent_activity), then=Value('active')),
        default=Value('inactive'),
        output_field=CharField()
    )
).values('id', 'username', 'activity_status')

7. Реальный пример: рекомендации постов

Получить посты для рекомендации (популярные, от подписанных авторов):

from django.db.models import Count, Exists, OuterRef, F, Subquery
from django.db.models.functions import Coalesce

# Подписан ли текущий пользователь на автора
subscribed_to_author = Subscription.objects.filter(
    subscriber_id=request.user.id,
    author_id=OuterRef('author_id')
).values('id')

# Посты с числом лайков и статусом подписки
recommended_posts = Post.objects.filter(
    is_published=True
).annotate(
    likes_count=Count('likes', distinct=True),
    is_from_subscribed=Exists(subscribed_to_author),
    score=Coalesce(F('likes_count') * 2, 0) + Case(
        When(is_from_subscribed=True, then=Value(5)),
        default=Value(0)
    )
).order_by('-score')[:20]

8. Сырой SQL vs ORM Subquery

Когда ORM становится сложным, можно использовать raw SQL:

from django.db.models import Subquery, F
from django.db import connection

# Вариант 1: Django ORM (сложный)
complex_query = User.objects.filter(...).annotate(...)

# Вариант 2: Сырой SQL (иногда проще)
with connection.cursor() as cursor:
    cursor.execute("""
        SELECT u.id, u.username, COUNT(c.id) as comment_count
        FROM auth_user u
        LEFT JOIN comments c ON u.id = c.user_id
        GROUP BY u.id
        HAVING COUNT(c.id) > 5
        ORDER BY comment_count DESC
    """)
    results = cursor.fetchall()

9. Оптимизация Subquery

Проблема: N+1 запрос

# Плохо: для каждого пользователя дополнительный запрос
for user in User.objects.all():
    comments = user.comment_set.all()  # N+1 запрос!

Решение: select_related и prefetch_related

# Хорошо: один запрос с джойном
users = User.objects.prefetch_related('comment_set').all()

# Или с annotate
users = User.objects.annotate(
    comment_count=Count('comment')
).values('id', 'username', 'comment_count')

10. Практический чеклист

Когда использовать Subquery:

  • Фильтрация по агрегированным значениям
  • Поиск записей с определённой связью
  • Многоуровневые условия
  • Получение дополнительных полей из связанных таблиц

Когда использовать Exists:

  • Просто проверка наличия связанных записей
  • Более эффективно чем Count для больших наборов

Когда писать raw SQL:

  • Сложные аналитические запросы
  • Запросы быстрее выполняются в SQL
  • Window функции, CTE и т.д.

Заключение

Subquery в Django — мощный инструмент для сложных запросов, но требует понимания SQL и производительности. Всегда проверяй select_related и prefetch_related перед использованием Subquery, так как они часто решают проблему проще и быстрее.