Класс Subquery
Класс Subquery применяется внутри выражений аннотации
или фильтрации для встраивания подзапроса в основной запрос.
Он принимает на вход QuerySet и позволяет использовать его
результат в качестве значения для поля, агрегации или условия.
Подзапросы выполняются в контексте внешнего запроса, что
делает их мощным инструментом для решения сложных задач
выборки данных.
Синтаксис
Subquery(queryset, output_field=None, **extra)
Параметры:
-
queryset- объектQuerySet, который будет выполнен как подзапрос. Должен возвращать одно значение или одну колонку. -
output_field- опциональный параметр, указывающий тип поля для результата подзапроса.
Пример
Используем Subquery для получения количества комментариев
к каждой статье:
from django.db.models import Subquery, Count, OuterRef
from myapp.models import Article, Comment
subquery = Comment.objects.filter(article=OuterRef('pk')).values('article').annotate(
total=Count('pk')
).values('total')
articles = Article.objects.annotate(comments_count=Subquery(subquery))
for article in articles:
print(f"{article.title}: {article.comments_count}")
Результат выполнения кода:
"article 1: 5"
"article 2: 3"
"article 3: 0"
Пример
Фильтрация статей по самому позднему комментарию с
использованием Subquery и агрегации:
from django.db.models import Subquery, Max, OuterRef
from myapp.models import Article, Comment
from datetime import datetime
subquery = Comment.objects.filter(
article=OuterRef('pk')
).values('article').annotate(
last_date=Max('created_at')
).values('last_date')
recent_articles = Article.objects.filter(
id__in=Subquery(
Comment.objects.filter(
created_at__gte=datetime(2025, 1, 1)
).values('article')
)
)
for article in recent_articles:
print(article.title)
Результат выполнения кода:
"article 1"
"article 3"
Пример
Сравнение значения поля с результатом подзапроса с
помощью Subquery в фильтрации:
from django.db.models import Subquery, F, OuterRef
from myapp.models import Product, Order
subquery = Order.objects.filter(
product=OuterRef('pk')
).values('product').annotate(
total_sum=Sum('quantity')
).values('total_sum')
products = Product.objects.annotate(
ordered_count=Subquery(subquery)
).filter(
ordered_count__gt=F('stock')
)
for product in products:
print(f"{product.name}: {product.ordered_count}")
Результат выполнения кода:
"product 1: 15"
"product 3: 8"