Какие основные способы выполнения JOIN-запросов существуют в Django ORM?

«Какие основные способы выполнения JOIN-запросов существуют в Django ORM?» — вопрос из категории Django, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Django ORM SQL-операция JOIN выполняется неявно при обращении к связанным моделям. Для оптимизации запросов и решения проблемы "N+1" существуют два ключевых метода:

  1. *`select_related(fields)** Этот метод "подтягивает" связанные объекты в одном SQL-запросе, используяLEFT OUTER JOIN. Он эффективен для связей "один-к-одному" (OneToOneField) и "многие-к-одному" (ForeignKey`).

    Проблема (N+1 запросов):

    # models.py
    # class Book(models.Model):
    #     author = models.ForeignKey(Author, on_delete=models.CASCADE)
    
    # views.py
    books = Book.objects.all() # 1-й запрос
    for book in books: 
        # На каждой итерации выполняется дополнительный запрос к таблице авторов
        print(book.author.name) # N дополнительных запросов

    Решение с select_related:

    # Выполняется один SQL-запрос с JOIN
    books = Book.objects.select_related('author').all()
    for book in books:
        # Данные об авторе уже загружены, дополнительный запрос не нужен
        print(book.author.name)
  2. *`prefetch_related(lookups)** Этот метод работает иначе: он выполняет отдельный запрос для связанных объектов и "соединяет" их в Python. Идеально подходит для связей "многие-ко-многим" (ManyToManyField) и обратных связейForeignKey`.

    Пример:

    # models.py
    # class Author(models.Model): ...
    # class Book(models.Model):
    #     authors = models.ManyToManyField(Author)
    
    # views.py
    # Получаем все книги и для каждой книги всех ее авторов
    books = Book.objects.prefetch_related('authors').all()
    for book in books:
        # authors.all() не генерирует новый запрос к БД
        author_names = [author.name for author in book.authors.all()]
        print(f"{book.title}: {', '.join(author_names)}")
  3. Неявный JOIN через filter() и exclude() Django автоматически создает JOIN, когда вы фильтруете по полям связанной модели, используя двойное подчеркивание __.

    # Найти все книги, написанные автором с именем 'Leo Tolstoy'
    # SQL: SELECT ... FROM book INNER JOIN author ON ... WHERE author.name = 'Leo Tolstoy'
    books = Book.objects.filter(author__name='Leo Tolstoy')

Сводка:

  • select_related: для ForeignKey и OneToOneField (один SQL-запрос с JOIN).
  • prefetch_related: для ManyToManyField и обратных связей (отдельный запрос и JOIN в Python).
  • filter(): для фильтрации по связанным полям (неявный JOIN).