Как в Django ORM использовать подзапросы и реализовать логику триггеров

Ответ

Django ORM предоставляет мощные инструменты для сложных запросов, включая подзапросы, но не имеет прямой поддержки для DB-триггеров, предлагая альтернативы на уровне приложения.

Подзапросы (Subqueries)

Подзапросы позволяют выполнять вложенные SELECT-запросы для аннотирования QuerySet, избегая выполнения множества отдельных запросов к базе данных. Для этого используются классы Subquery и OuterRef.

  • OuterRef: Ссылается на поле из внешнего запроса, связывая подзапрос с основным.

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

from django.db.models import Subquery, OuterRef, CharField
from .models import Post, Comment

# Подзапрос для получения последнего комментария
latest_comment_subquery = Comment.objects.filter(
    post=OuterRef('pk')  # Ссылка на pk внешнего Post
).order_by('-created_at')

# Аннотируем основной QuerySet постов
posts = Post.objects.annotate(
    last_comment_text=Subquery(
        latest_comment_subquery.values('text')[:1],
        output_field=CharField()
    )
)

Реализация логики триггеров

Поскольку ORM не управляет триггерами на уровне БД, их логику реализуют на уровне приложения. Наиболее распространенный подход — сигналы (signals).

  1. Сигналы Django: Это хуки, которые позволяют вашему коду получать уведомления о действиях, происходящих в фреймворке, например, до или после сохранения модели.

    • pre_save / post_save: Срабатывают до/после вызова метода save().
    • pre_delete / post_delete: Срабатывают до/после удаления объекта.
    from django.db.models.signals import post_save
    from django.dispatch import receiver
    from .models import User, UserProfile
    
    @receiver(post_save, sender=User)
    def create_user_profile(sender, instance, created, **kwargs):
        # Если пользователь только что создан, создаем для него профиль
        if created:
            UserProfile.objects.create(user=instance)
  2. Переопределение методов модели: Можно переопределить методы save() или delete() для добавления кастомной логики.

  3. Raw SQL в миграциях: Для создания нативных триггеров в БД можно использовать migrations.RunSQL в файлах миграций Django.