- en
- Language: ru
- Documentation version: 2.0
7. Как сделать подзапросное выражение в Django?
Django позволяет использовать подзапросы SQL. Давайте начнем с чего-то простого, У нас есть модель UserParent
, которая имеет OnetoOne
отношение с пользователем auth. Мы найдем все UserParent
, у которых есть UserParent
.
>>> from django.db.models import Subquery
>>> users = User.objects.all()
>>> UserParent.objects.filter(user_id__in=Subquery(users.values('id')))
<QuerySet [<UserParent: UserParent object (2)>, <UserParent: UserParent object (5)>, <UserParent: UserParent object (8)>]>
Теперь о чем-то более сложном. Для каждого Category
мы хотим найти наиболее благосклонного Hero
.
Модели выглядят примерно так.
class Category(models.Model):
name = models.CharField(max_length=100)
class Hero(models.Model):
# ...
name = models.CharField(max_length=100)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
benevolence_factor = models.PositiveSmallIntegerField(
help_text="How benevolent this hero is?",
default=50
)
Вы можете найти самого доброжелательного Героя следующим образом
hero_qs = Hero.objects.filter(
category=OuterRef("pk")
).order_by("-benevolence_factor")
Category.objects.all().annotate(
most_benevolent_hero=Subquery(
hero_qs.values('name')[:1]
)
)
Если вы посмотрите на сгенерированный sql, вы увидите
SELECT "entities_category"."id",
"entities_category"."name",
(SELECT U0."name"
FROM "entities_hero" U0
WHERE U0."category_id" = ("entities_category"."id")
ORDER BY U0."benevolence_factor" DESC
LIMIT 1) AS "most_benevolent_hero"
FROM "entities_category"
Давайте разберем логику кверисета. Первая часть
hero_qs = Hero.objects.filter(
category=OuterRef("pk")
).order_by("-benevolence_factor")
Мы упорядочиваем объект Hero
по benevolence_factor
в порядке DESC и используем category=OuterRef("pk")
, чтобы объявить, что мы будем использовать его в подзапросе.
Затем мы аннотируем most_benevolent_hero=Subquery(hero_qs.values('name')[:1])
, чтобы получить использование подзапроса с кверисетом Category
. Часть hero_qs.values('name')[:1]
подхватывает первое имя из подзапроса.