Как работает join в Django
Что проверяют
Понимаете ли вы, что метода join в Django ORM нет, и видите ли, в какой момент ORM сам строит SQL JOIN. Следом обычно просят объяснить разницу между select_related и prefetch_related и рассказать про проблему N+1.
Как отвечать
JOIN появляется в запросе в нескольких случаях:
- фильтр через связь:
Book.objects.filter(author__country="RU")с двойным подчёркиванием даёт JOIN таблицы авторов. То же сorder_byиvalues("author__name"); select_related: подтягивает связанный объект тем же запросом через JOIN. Работает дляForeignKeyиOneToOneFieldв прямую сторону и для обратной стороныOneToOneField. Если внешний ключ допускаетNULL, Django ставитLEFT OUTER JOIN, иначеINNER JOIN;- агрегация:
Author.objects.annotate(n=Count("book"))строит JOIN сGROUP BY.
prefetch_related JOIN не делает. Он выполняет отдельный запрос вида WHERE author_id IN (...) и склеивает результаты в Python. Это путь для ManyToManyField и обратных связей внешнего ключа, где JOIN размножил бы строки основной таблицы. Тонко настроить подгрузку можно объектом Prefetch с собственным queryset.
Покажите, как вы проверяете результат: print(qs.query) выводит SQL, django-debug-toolbar показывает число запросов на странице, в тестах помогает assertNumQueries. Типовая история: в цикле по книгам обращаются к book.author.name, и на сто книг выходит сто один запрос. select_related("author") сводит это к одному.
Где спотыкаются
Говорят, что select_related подходит для «один ко многим» с любой стороны. Для обратной связи (author.book_set) он не работает, там нужен prefetch_related.
Фильтруют по многозначной связи и получают дубликаты: Author.objects.filter(book__title__icontains="django") вернёт автора столько раз, сколько у него подходящих книг. Лечится distinct() или подзапросом через Exists.
Ставят несколько annotate с Count по разным связям. JOIN перемножает строки, и счётчики выходят завышенными. Помогают Count(..., distinct=True) или подзапросы через Subquery.