annotate와 aggregate로 DB 레벨에서 데이터 분석/통계내기

Django ORM의 aggregate와 annotate를 활용해 Python이 아닌 DB 레벨에서 효율적으로 데이터 통계 및 분석을 처리하는 방법을 학습하고, F 객체, Q 객체, Subquery와 조합한 복잡한 쿼리 작성 기법을 익힘

핵심 개념: aggregate vs annotate

구분
aggregate
annotate

목적

QuerySet 전체에 대한 요약/통계 값 계산

QuerySet의 각 항목에 대한 주석(계산 필드) 추가

반환값

단일 dict

새로운 QuerySet

사용 예시

전체 상품의 평균 가격, 총 재고 수량

각 상품별 리뷰 개수, 각 카테고리별 상품 수

aggregate(): 전체에 대한 통계

# 전체 상품의 평균 가격, 총 재고 수량
summary_data = Product.objects.aggregate(
    avg_price=Avg('price'), 
    total_stock=Sum('stock')
)

# 가격 범위
price_range = Product.objects.aggregate(
    max_price=Max('price'),
    min_price=Min('price')
)

annotate(): 개별 객체에 정보 추가

# 각 상품별 리뷰 개수 추가
products_with_review_count = Product.objects.annotate(
    review_count=Count('reviews')
)

# 평균 평점 추가 및 정렬
popular_products = Product.objects.annotate(
    avg_rating=Avg('reviews__rating'),
    review_count=Count('reviews')
).order_by('-review_count')

# annotate된 필드로 필터링
highly_reviewed_products = Product.objects.annotate(
    review_count=Count('reviews')
).filter(review_count__gte=10)

values()와 annotate() 순서

  • values() → annotate(): GROUP BY 효과 발생

  • annotate() → values(): 단순히 출력할 필드 제한

# 카테고리별 상품 개수
Category.objects.values('name').annotate(num_products=Count('product'))

# 상품별 리뷰 개수 중 특정 필드만 출력
Product.objects.annotate(review_count=Count('reviews')).values('name', 'review_count')

고급 테크닉

F 객체로 필드 값 연산

# 할인가 계산
Product.objects.annotate(
    discounted_price=F('price') * (1 - F('discount_rate'))
)

Q 객체로 조건부 집계

# 전체 리뷰 수와 긍정 리뷰 수 동시 계산
Product.objects.annotate(
    total_reviews=Count('reviews'),
    positive_reviews=Count('reviews', filter=Q(reviews__rating__gte=4))
)

Subquery로 복잡한 N+1 해결

# 각 상품의 가장 최근 리뷰 평점
latest_review_rating = Review.objects.filter(
    product=OuterRef('pk')
).order_by('-created_at').values('rating')[:1]

products_with_latest_rating = Product.objects.annotate(
    latest_rating=Subquery(latest_review_rating)
)

distinct 옵션

# 유니크한 구매자 수 계산
Category.objects.annotate(
    unique_buyers=Count('products__order_items__order__user', distinct=True)
)

디버깅

# 생성된 SQL 확인
queryset = Product.objects.annotate(review_count=Count('reviews'))
print(str(queryset.query))

Last updated