Какие инструменты Django ORM используются для создания сложных запросов?

Ответ

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

Основные инструменты:

  • annotate() и aggregate(): Используются для выполнения агрегирующих функций (например, Count, Sum, Avg) и добавления вычисляемых полей к каждому объекту в QuerySet. aggregate() возвращает словарь с агрегированными значениями для всего QuerySet, а annotate() добавляет аннотированное поле к каждому объекту.
  • F()-объекты: Позволяют ссылаться на поля модели в запросах, что полезно для сравнения двух полей одной модели или выполнения арифметических операций над ними непосредственно в базе данных.
  • Q()-объекты: Используются для построения сложных логических условий (AND, OR, NOT) в запросах, объединяя несколько условий фильтрации. Это позволяет создавать более гибкие и читаемые условия.
  • select_related() и prefetch_related(): Методы для оптимизации запросов к связанным моделям и решения проблемы N+1 запросов. select_related() выполняет SQL JOIN и загружает связанные объекты в одном запросе (для отношений "один-ко-многим" и "один-к-одному"), а prefetch_related() выполняет отдельные запросы для каждой связанной модели и объединяет их в Python (для отношений "многие-ко-многим" и "обратных" отношений).
  • Subquery и Window functions: Для выполнения подзапросов и использования оконных функций базы данных (например, ROW_NUMBER(), RANK()) для более сложной аналитики и ранжирования данных.

Пример сложного запроса с annotate и Subquery:

Предположим, у нас есть модели Author и Book, и мы хотим получить всех авторов с количеством их книг.

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

# Пример моделей (для контекста)
class Author(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

    def __str__(self):
        return self.title

# Подзапрос для подсчета книг каждого автора
# OuterRef('pk') ссылается на pk текущего автора из внешнего запроса
book_count_subquery = Book.objects.filter(
    author=OuterRef('pk')
).values('author').annotate(
    total_books=Count('id')
).values('total_books')[:1] # [:1] для получения одного значения из подзапроса

# Основной запрос: аннотируем авторов количеством их книг
authors_with_book_counts = Author.objects.annotate(
    num_books=Subquery(book_count_subquery, output_field=models.IntegerField())
)

for author in authors_with_book_counts:
    print(f"Автор: {author.name}, Книг: {author.num_books if author.num_books is not None else 0}")

В случаях, когда ORM не предоставляет достаточной гибкости или производительности, можно использовать raw SQL через методы raw() для получения QuerySet-подобных объектов или connection.cursor() для выполнения произвольных SQL-запросов.

Ответ 18+ 🔞

Слушай, а вот этот твой Django ORM — это ж, блядь, просто песня, а не инструмент! Сидишь себе, попиваешь кофеёк, а он там в недрах базы данных такие пируэты выписывает, что мама не горюй. И главное — SQL руками почти не пишешь, ёпта!

Вот смотри, какие у него фишки есть, просто овердохуища:

  • annotate() и aggregate(): Это как два брата-близнеца, но один — зануда, а второй — душа компании. aggregate() — тот самый зануда: посчитает что-то по всей таблице (сумму, среднее, хуй с горы) и выдаст один сухой словарик. А annotate() — весёлый парень: к каждой записи в результатах прилепит вычисленное поле. Хочешь к каждому автору количество книг прикрутить? Легко!
  • F()-объекты: Вообще гениальная хуйня. Позволяет в запросе одно поле модели с другим сравнивать, прямо в базе, без вытаскивания всего на свете в память. Типа: «найди всех, у кого количество просмотров больше количества лайков». База сама всё посчитает, красота!
  • Q()-объекты: А это, сука, для настоящих гурманов логики. Когда простого filter() уже мало и хочется склеить условия через «ИЛИ», «И», «НЕ». Чувствуешь себя повелителем булевой алгебры, блядь!
  • select_related() и prefetch_related(): Вот это, блядь, святое! Без них твоё приложение сожрёт базу запросами как мартышка банан. select_related() — для связей «один к одному» или «ко многим от одного» (foreign key), делает JOIN и тащит всё за раз. prefetch_related() — для «многие ко многим», хитро предзагружает связки отдельными запросами. Пользоваться — обязательно, иначе N+1 проблему получишь в полный рост, пиздец.
  • Subquery и оконные функции: Ну это уже для циркачей высшей категории. Когда нужно внутри запроса ещё один запрос встроить или ранжирование какое-то сделать (типа «присвоить порядковый номер записям в группе»). Мощно, но голова может болеть.

Вот, смотри, пример, чтобы не быть голословным, как annotate с Subquery дружат:

Допустим, есть автор и книги. Надо авторов вывести, и рядом — сколько книг у каждого. Проще простого!

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

# Модели, всё как обычно, хули тут думать
class Author(models.Model):
    name = models.CharField(max_length=100)

    def __str__(self):
        return self.name

class Book(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

    def __str__(self):
        return self.title

# Подзапрос: для каждого автора (OuterRef('pk')) считаем книжки
book_count_subquery = Book.objects.filter(
    author=OuterRef('pk')
).values('author').annotate(
    total_books=Count('id')
).values('total_books')[:1]  # Берём только одно значение, а то нахуй нам список

# Главный запрос: цепляем эту цифру к каждому автору как новое поле
authors_with_book_counts = Author.objects.annotate(
    num_books=Subquery(book_count_subquery, output_field=models.IntegerField())
)

# И поехали!
for author in authors_with_book_counts:
    print(f"Автор: {author.name}, Книг: {author.num_books if author.num_books is not None else 0}")

А если вдруг, блядь, ORM упрётся в потолок и не сможет какую-то дичь вытворять (бывает, чёрт побери!), то всегда есть сырой SQL. Через raw() или connection.cursor(). Но это уже как ножевой бой — мощно, но можно и по пальцам порезаться, если не умеешь.