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

Выполнял ли сложные запросы в Django ORM

2.2 Middle🔥 161 комментариев
#Django#Базы данных (SQL)

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

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

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

Выполнял ли сложные запросы в Django ORM

Да, много. Django ORM может быть как простым так и сложным.

Простые запросы

# Базовые CRUD
User.objects.all()
User.objects.get(id=1)
User.objects.filter(email='test@example.com')
User.objects.create(name='John', email='john@example.com')

Сложные запросы которые я делал

1. Аннотирование и агрегирование

from django.db.models import Count, Sum, Avg, Q

# Сколько постов у каждого пользователя
users = User.objects.annotate(
    post_count=Count('posts')
).filter(
    post_count__gt=5  # Больше 5 постов
)

# Средняя оценка поста по авторам
authors = User.objects.annotate(
    avg_rating=Avg('posts__rating')
).order_by('-avg_rating')

# Сумма всех заказов по категориям
categories = Category.objects.annotate(
    total_sales=Sum('products__orders__amount')
)

2. Сложные фильтры с Q объектами

from django.db.models import Q

# Пользователи которые либо админы либо создали >10 постов
users = User.objects.filter(
    Q(is_admin=True) | Q(posts__count__gt=10)
).distinct()

# Исключающие условия (NOT)
posts = Post.objects.filter(
    Q(published=True) & ~Q(status='draft')
)

# Комплексная логика
orders = Order.objects.filter(
    (Q(status='pending') & Q(created_at__lt=yesterday)) |
    (Q(status='processing') & Q(created_at__lt=last_week))
)

3. Select_related и prefetch_related

# Без оптимизации = N+1 проблема
posts = Post.objects.all()
for post in posts:
    print(post.author.name)  # Каждый раз новый SQL запрос!

# С select_related (для ForeignKey)
posts = Post.objects.select_related('author', 'category')
for post in posts:
    print(post.author.name)  # Уже загружено в памяти

# С prefetch_related (для reverse ForeignKey и ManyToMany)
authors = User.objects.prefetch_related('posts')
for author in authors:
    for post in author.posts.all():  # Уже загружено
        print(post.title)

# Custom Prefetch для фильтрования
from django.db.models import Prefetch
published_posts = Post.objects.filter(published=True)
authors = User.objects.prefetch_related(
    Prefetch('posts', queryset=published_posts)
)

4. F выражения

from django.db.models import F, Value
from django.db.models.functions import Concat

# Обновить поле основываясь на другом поле
Order.objects.all().update(
    total_price=F('price') * F('quantity')
)

# Увеличить значение
Product.objects.filter(id=1).update(
    views=F('views') + 1
)

# Конкатенировать строки
users = User.objects.annotate(
    full_name=Concat(
        F('first_name'),
        Value(' '),
        F('last_name')
    )
)

5. Сложные JOIN'ы и через несколько таблиц

# Orders где товар из категории X и статус Y
orders = Order.objects.filter(
    items__product__category__name='Electronics',
    status='completed'
).distinct()

# Все посты которые лайкнула девушка
posts = Post.objects.filter(
    likes__user__gender='female'
).distinct()

# Двойная связь через m2m
authors_who_collab = Author.objects.filter(
    books__contributors=Author.objects.get(name='Alice')
).exclude(
    id=Author.objects.get(name='Alice').id
)

6. Case/When для условной логики

from django.db.models import Case, When, Value, IntegerField

# Скор на основе условий
users = User.objects.annotate(
    score=Case(
        When(is_admin=True, then=Value(100)),
        When(posts__count__gte=50, then=Value(80)),
        When(posts__count__gte=10, then=Value(50)),
        default=Value(0),
        output_field=IntegerField()
    )
)

# Категория на основе цены
products = Product.objects.annotate(
    price_category=Case(
        When(price__lt=100, then=Value('cheap')),
        When(price__lt=1000, then=Value('medium')),
        default=Value('expensive')
    )
)

7. Values и Values_list

# Только конкретные поля (для экономии памяти)
user_names = User.objects.values_list('id', 'name')
# [1, 'John'], [2, 'Jane']

# Как словари
users = User.objects.values('id', 'name', 'email')
# [{'id': 1, 'name': 'John', 'email': 'john@example.com'}]

# С аннотирование
top_users = User.objects.values('id', 'name').annotate(
    post_count=Count('posts')
).order_by('-post_count')[:10]

8. Raw SQL когда ORM не может

# Если ORM слишком сложный - raw SQL
users = User.objects.raw('''
    SELECT u.id, u.name, COUNT(p.id) as post_count
    FROM users u
    LEFT JOIN posts p ON u.id = p.user_id
    WHERE p.created_at > %s
    GROUP BY u.id, u.name
    ORDER BY post_count DESC
''', [start_date])

# Или через extra (но лучше raw)
posts = Post.objects.extra(
    select={'author_posts': 'SELECT COUNT(*) FROM posts WHERE user_id=posts.user_id'}
)

# Или через cursor для очень сложного
from django.db import connection
with connection.cursor() as cursor:
    cursor.execute('SELECT ...', [params])
    result = cursor.fetchall()

9. Оптимизация с only/defer

# Загрузить только нужные поля
users = User.objects.only('id', 'name')
# Не загружает password, email и т.д.

# Загрузить всё кроме больших полей
posts = Post.objects.defer('content')
# Загружает title, author но не content

10. Distinct и Group By

# Уникальные авторы которые написали о Django
authors = User.objects.filter(
    posts__tags__name='django'
).distinct()

# Не то же самое что GROUP BY!
# GROUP BY требует aggregate в SELECT
from django.db.models import Count
authors = User.objects.filter(
    posts__tags__name='django'
).values('id', 'name').annotate(
    post_count=Count('id')
).order_by('-post_count')

Ошибки которые я совершал

Ошибка 1: N+1 запросы

# ПЛОХО
posts = Post.objects.all()
for post in posts:
    print(post.author.name)  # 1000 запросов!

# ХОРОШО
posts = Post.objects.select_related('author')
for post in posts:
    print(post.author.name)  # 1 запрос

Ошибка 2: Неправильный distinct

# ПЛОХО - не удаляет дубли
posts = Post.objects.filter(
    likes__user=user
).distinct()  # Не помогает

# ХОРОШО - сначала фильтруем потом distinct
posts = Post.objects.filter(
    likes__user=user
).select_related('author').distinct()

Ошибка 3: Слишком много аннотирований

# ПЛОХО - медленно
users = User.objects.annotate(
    post_count=Count('posts'),
    comment_count=Count('comments'),
    like_count=Count('likes'),
    follower_count=Count('followers')
)

# ХОРОШО - кэшируем в отдельные поля
# Или считаем в приложении отдельно

Когда переходить на Raw SQL

# Используй ORM когда:
# - Простые запросы
# - Нужна переносимость БД
# - Есть встроенные методы

# Используй Raw SQL когда:
# - Очень сложные запросы
# - Нужна максимальная производительность
# - ORM не может это выразить
# - Используешь специфичные для БД функции

Инструменты для дебага

from django.db import connection

# Посмотри какой SQL сгенерировал ORM
print(Post.objects.filter(author__name='John').query)

# Посмотри все запросы в запросе
from django.db import connection
from django.test.utils import override_settings
@override_settings(DEBUG=True)
def my_view():
    # ...код...
    print(len(connection.queries))  # Количество запросов
    for q in connection.queries:
        print(q['sql'])  # Сам SQL
        print(q['time'])  # Время выполнения

Реальные примеры из проектов

1. Рекомендации для пользователей

from django.db.models import Q, Count

# Товары которые нравятся друзьям пользователя
def get_recommendations(user):
    return Product.objects.filter(
        likes__user__in=user.friends.all(),
        ~Q(likes__user=user)  # Не лайкнул сам
    ).annotate(
        score=Count('likes')
    ).order_by('-score')[:10]

2. Статистика по времени

from datetime import timedelta
from django.utils import timezone
from django.db.models import Count

# Активные пользователи за последнюю неделю
week_ago = timezone.now() - timedelta(days=7)
active_users = User.objects.filter(
    last_activity__gte=week_ago
).annotate(
    action_count=Count('actions')
).filter(
    action_count__gte=5
)

3. Поиск с рангированием

from django.db.models import Q, F, Value
from django.db.models.functions import Length

# Поиск со скором релевантности
query = 'django tutorial'
posts = Post.objects.filter(
    Q(title__icontains=query) |
    Q(content__icontains=query)
).annotate(
    relevance=Case(
        When(title__icontains=query, then=Value(10)),
        When(content__icontains=query, then=Value(1)),
        output_field=IntegerField()
    )
).order_by('-relevance')

Когда я использовал Raw SQL

# Рекурсивная иерархия (мой проект)
raw_sql = '''
WITH RECURSIVE category_tree AS (
    SELECT id, name, parent_id FROM categories WHERE parent_id IS NULL
    UNION ALL
    SELECT c.id, c.name, c.parent_id FROM categories c
    INNER JOIN category_tree ct ON c.parent_id = ct.id
)
SELECT * FROM category_tree
'''
categories = Category.objects.raw(raw_sql)

Заключение

  • Django ORM покрывает 80% случаев отлично
  • Для 20% нужен Raw SQL или знание ORM
  • Я знаю оба пути
  • Оптимизирую с select_related/prefetch_related
  • Если ORM становится слишком сложным - пишу Raw SQL
  • Всегда используй EXPLAIN ANALYZE для дебага
  • Профилируй перед оптимизацией
Выполнял ли сложные запросы в Django ORM | PrepBro