7. 장고에서 서브쿼리 식을 사용할 수 있나요?

장고에서 SQL 서브쿼리(subquery, 질의문 내의 하위 질의) 식을 사용할 수 있습니다. 간단한 것부터 시작해 봅시다. auth_user 모델과 일 대 일(OneToOne) 관계로 연결된 UserParent 모델이 있다고 합시다. 아래 코드로 UserParent 모델에서 auth_user 를 가진 행을 모두 구할 수 있습니다.

>>> 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)에 따라 내림차순으로 정렬하여 선택합니다. 그리고 category=OuterRef("pk") 를 이용해 이 선택이 서브쿼리로 사용될 수 있도록 준비합니다.

그 뒤 most_benevolent_hero=Subquery(hero_qs.values('name')[:1]) 로 서브쿼리에 별칭을 붙여 Category 쿼리셋 안에서 사용합니다. 이 때, hero_qs.values('name')[:1] 는 서브쿼리에서 첫 번째 행의 name 필드를 구하는 코드입니다.