-
[Django] OuterRef, Subquery 사용할 때, .count() 오류 해결법언어/파이썬 & 장고 2020. 4. 27. 20:02
Select 절에 inner 쿼리 형태를 만들고 inner 쿼리의 결과로 count를 세고자 할 때 아래와 같은 쿼리를 짤 수 있습니다.
SELECT id, title, (SELECT COUNT(tag.id) AS count FROM tag INNER JOIN post ON (post.id = tag.id) GROUP BY tag.id ) AS "count" FROM post
이러한 형태를 ORM으로 변형하면 다음과 같은 형태로 표현할 수 있습니다.
from django.db import models from django.db.models import OuterRef, Subquery class Tag(models.Model): name = models.CharField(max_length=120) class Post(models.Model): title = models.CharField(max_length=120) tags = models.ManyToManyField(Tag) tag1 = Tag.objects.create(name='tag1') post1 = Post.objects.create(title='post1') post1.tags.add(tag1) tags_list = Tag.objects.filter(post=OuterRef('pk')) Post.objects.annotate(count=Subquery(tags_list.count())) # 오류 발생 # ValueError: This queryset contains a reference to an outer query and may only be used in a subquery.
Subquery() 내부에 count() 함수를 사용해 구현을 하면 위와 같은 오류가 발생합니다. OuterRef() 함수는 Subquery() 함수 내에서만 사용이 가능하므로 .count() 메소드를 사용할 수 없습니다. 이러한 문제를 해결하기 위해 Count() 함수를 사용해야 합니다.
from django.db.models import OuterRef, Subquery, Count queryset = Post.objects.annotate( count=Count(Subquery(Tag.objects.filter(post=OuterRef('pk')).only('pk'))) ) print(queryset.query) # 아래 쿼리 또한 원하는 형태가 아님 # SELECT # "tests_post"."id", # "tests_post"."title", # COUNT((SELECT U0."id" # FROM "tests_tag" U0 # INNER JOIN "tests_post_tags" U1 ON (U0."id" = U1."tag_id") # WHERE U1."post_id" = ("tests_post"."id")) # ) AS "count" # FROM "tests_post" # GROUP BY # "tests_post"."id", # "tests_post"."title"
위의 쿼리를 보면 GROUP BY가 자동으로 걸려서 추출하고자 하는 쿼리 형태가 아닙니다. Count() 함수는 aggregate 이므로 select 절로 나오는 컬럼들을 자동으로 group by에 추가하게 됩니다. 이 문제를 해결하기 위해 values() + annotate() + values()를 사용해야 합니다.
from django.db.models import OuterRef, Subquery, Count queryset = Post.objects.annotate( count=Subquery( Tag.objects.filter(post=OuterRef('pk')) .values('post') .annotate(count=Count('pk')) .values('count') ) ) print(queryset.query) # SELECT # "tests_post"."id", # "tests_post"."title", # (SELECT COUNT(U0."id") AS "count" # FROM "tests_tag" U0 # INNER JOIN "tests_post_tags" U1 ON (U0."id" = U1."tag_id") # WHERE U1."post_id" = ("tests_post"."id") # GROUP BY U1."post_id" # ) AS "count" # FROM "tests_post"
첫 번째 values()를 누락하면 select 절의 inner 쿼리의 group by에 더 많은 컬럼이 추가될 수 있습니다. 따라서 group by로 묶고자 하는 컬럼을 반드시 명시해야 합니다.