Ответ
Subquery
в Django ORM — это объект, который позволяет создавать вложенные SQL-запросы. Его основное преимущество — возможность выполнять сложные выборки и аннотации, которые зависят от другой таблицы, за один-единственный запрос к базе данных, избегая проблемы N+1.
Практический пример
Допустим, у нас есть модели Post
и Comment
, и мы хотим для каждого поста получить текст его последнего комментария.
1. Модели:
# models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
text = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
2. Запрос с Subquery
:
# views.py
from django.db.models import OuterRef, Subquery
from .models import Post, Comment
# Создаем подзапрос, который для каждого поста (OuterRef('pk'))
# найдет самый новый комментарий.
latest_comment = Comment.objects.filter(
post=OuterRef('pk')
).order_by('-created_at')
# Аннотируем каждый пост текстом последнего комментария.
# Подзапрос `latest_comment.values('text')[:1]` вернет
# только одно значение — текст одного комментария.
posts_with_last_comment = Post.objects.annotate(
last_comment_text=Subquery(latest_comment.values('text')[:1])
)
# Теперь у каждого объекта `post` в `posts_with_last_comment`
# будет дополнительное поле `last_comment_text`.
Ключевые аспекты
OuterRef
: Используется для ссылки на поле из внешнего запроса (в примере —pk
моделиPost
).- Эффективность: Вся операция выполняется на уровне СУБД одним SQL-запросом, что гораздо производительнее, чем делать отдельные запросы для каждого объекта в цикле.
- Ограничение результата: Подзапрос, используемый для аннотации или фильтрации, должен возвращать ровно одно значение (одну строку и один столбец). Для этого часто используется срез
[:1]
.