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
