← Назад к вопросам
Как делать 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 проблемы предзагрузкой
- Логируй запросы для проверки оптимизации