Как в 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.

Ответ 18+ 🔞

Да ты посмотри, какие у нас тут инструменты подвезли! Django ORM, блядь, как швейцарский нож для запросов — и подзапросы, и аннотации, а вот с триггерами прям беда, сука. На уровне базы их нет, приходится выкручиваться на уровне приложения. Ну что ж, разберёмся, как тут без потерь.

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

Вот это, блядь, мощная штука, чтобы не дёргать базу как последнего распиздяя отдельными запросами. Берёшь Subquery и OuterRef — и вуаля, всё в один заход.

  • OuterRef: Это как крикнуть из подвала наверх: «Эй, я тут твой pk вижу, ща привяжусь!». Связывает подзапрос с основным.

Пример: Вытащить текст последнего коммента к каждому посту

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

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

# И накручиваем это дело на посты
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 в миграциях: Если прям приперло и нужны нативные триггеры прямо в БД, то в миграциях Django есть migrations.RunSQL. Пишешь сырой SQL и запускаешь, но это уже на свой страх и риск, чувак. Один неверный запрос — и прощай, данные, в рот меня чих-пых!

Вот так вот, не всё так гладко, но жить можно. Главное — понимать, где что применять, чтобы не выстрелить себе в ногу.