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

Как делать JOIN в Django ORM?

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

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

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

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

Виды JOINов в Django ORM

Django ORM автоматически выполняет INNER JOIN при обращении к полям связанных моделей. Основные способы реализации JOINов:

1. Неявный JOIN через точку (Double Underscore)

Это самый распространённый способ. Django автоматически создаёт JOIN при обращении к полям связанной модели через __:

from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

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

# Найти все книги авторов, чьё имя начинается с "J"
books = Book.objects.filter(author__name__startswith="J")
# SQL: SELECT * FROM book JOIN author ON book.author_id = author.id WHERE author.name LIKE "J%"

# Получить объекты Book с предзагруженными данными автора
books = Book.objects.filter(author__name="John")
for book in books:
    print(book.author.name)  # Уже в памяти, нет дополнительного запроса

2. select_related() — для ForeignKey и OneToOneField

Предзагружает связанные объекты в один SQL запрос:

# Плохо — N+1 проблема
books = Book.objects.all()
for book in books:
    print(book.author.name)  # Дополнительный запрос для каждой книги

# Хорошо — один JOIN запрос
books = Book.objects.select_related("author")
for book in books:
    print(book.author.name)  # Уже в памяти

# Цепочка связей
books = Book.objects.select_related("author", "publisher")

# Глубокая цепочка
comments = Comment.objects.select_related("post__author__profile")

3. prefetch_related() — для ManyToManyField и reverse ForeignKey

Выполняет несколько запросов, но оптимизированно:

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    title = models.CharField(max_length=200)
    authors = models.ManyToManyField(Author)  # Many-to-many

# Плохо — N+1 для каждого автора в каждой книге
books = Book.objects.all()
for book in books:
    for author in book.authors.all():  # Дополнительный запрос
        print(author.name)

# Хорошо — 2 запроса вместо N*M
books = Book.objects.prefetch_related("authors")
for book in books:
    for author in book.authors.all():  # Уже в памяти
        print(author.name)

4. Явный JOIN через select_related и filter

# Комбинация select_related и filter
books = Book.objects.select_related("author").filter(
    author__profile__country="USA"
)

# Результат: INNER JOIN book → author → profile с фильтром

5. LEFT JOIN через values() иAnnotateCount

from django.db.models import Count, F, Q
from django.db.models import Prefetch

# LEFT JOIN для сохранения авторов без книг
authors = Author.objects.annotate(
    book_count=Count("book", distinct=True)
).filter(book_count__gte=0)  # Включит авторов с 0 книг

6. Сложные JOINы через raw SQL (если нужна особая логика)

from django.db import connection

with connection.cursor() as cursor:
    cursor.execute("""
        SELECT b.id, b.title, a.name
        FROM book b
        LEFT JOIN author a ON b.author_id = a.id
        WHERE a.country = %s
    """, ["USA"])
    results = cursor.fetchall()

Оптимизация JOINов

# Комбинированный подход
books = (
    Book.objects
    .select_related("author")  # INNER JOIN для ForeignKey
    .prefetch_related("authors")  # Отдельный запрос для M2M
    .filter(author__country="USA")
)

# Использование only() и defer() для уменьшения полей
books = Book.objects.select_related("author").only(
    "title", "author__name"
)  # Загружает только эти поля

Проверка сгенерированного SQL

from django.test.utils import override_settings
from django.db import connection
from django.conf import settings

# Включи логирование запросов
with override_settings(DEBUG=True):
    books = Book.objects.select_related("author")
    for book in books:
        print(book.author.name)

print(f"Количество запросов: {len(connection.queries)}")
for query in connection.queries:
    print(query["sql"])

Итог

  • select_related() для ForeignKey и OneToOne (INNER JOIN)
  • prefetch_related() для ManyToMany и reverse relations
  • double underscore (__) для фильтров через связи
  • Избегай N+1 проблемы предзагрузкой
  • Логируй запросы для проверки оптимизации
Как делать JOIN в Django ORM? | PrepBro