annotate()
는 새로운 QuerySet을 반환하며, 다음과 같은 특징이 있습니다:
# 기본적인 annotate 사용
posts = Post.objects.annotate(
comment_count=Count('comments')
)
# 이 시점에서는 아직 쿼리가 실행되지 않음
# 실제 데이터가 필요한 시점에 쿼리 실행
for post in posts: # 이 시점에 쿼리 실행
print(post.comment_count)
posts = Post.objects.annotate(
comment_count=Count('comments')
).filter(
comment_count__gt=5
).order_by('-comment_count')
# 쿼리가 실행되지 않는 시점
posts = Post.objects.all() # 쿼리 미실행
posts = posts.annotate(comment_count=Count('comments')) # 여전히 미실행
# 쿼리가 실행되는 시점
post_list = list(posts) # 이때 실행
posts = Post.objects.annotate(
comment_count=Count('comments')
).annotate(
like_count=Count('likes')
).filter(
comment_count__gt=5
)
# 좋은 예제
for post in Post.objects.annotate(comment_count=Count('comments')):
# 한 번에 하나의 레코드만 메모리에 로드
print(post.comment_count)
# 주의가 필요한 예제
posts = list(Post.objects.annotate(comment_count=Count('comments')))
# 모든 레코드를 한 번에 메모리에 로드
# 비효율적인 쿼리
posts = Post.objects.all() # 첫 번째 쿼리
count = posts.annotate(comment_count=Count('comments')) # 두 번째 쿼리
# 최적화된 쿼리
posts = Post.objects.annotate(comment_count=Count('comments')) # 단일 쿼리
다음 Django ORM 코드는:
Post.objects.annotate(comment_count=Count('comments'))
이런 SQL로 변환됩니다:
SELECT
"post".*,
COUNT("comments"."id") as "comment_count"
FROM "post"
LEFT OUTER JOIN "comments" ON "post"."id" = "comments"."post_id"
GROUP BY "post"."id";
annotate()
는 단순히 필드를 추가하는 것이 아니라, QuerySet의 모든 특성을 유지하면서 새로운 계산 필드를 추가하는 강력한 도구입니다. Lazy Evaluation 덕분에 효율적인 쿼리 실행이 가능하며, 체이닝을 통해 복잡한 쿼리도 깔끔하게 작성할 수 있습니다.