django annotate
Django annotate
Django ORM에서 annotate()는 쿼리셋에 새로운 필드를 추가하고 이를 기반으로 정렬하거나 필터링하는 데 사용됨.
특히 Count, Sum, Avg 등 집계 함수와 함께 사용
1. annotate 란
annotate()는 각 객체에 새로운 필드 값을 계산해서 추가하는 역할
주로 Count, Sum, Avg, Max와 같은 집계 함수와 함께 사용되며 SQL의 GROUP BY 절과 비슷하게 동작함
2. annotate 기본 사용법
예시로 상품 데이터 모델인 Product가 있고
찜 기능을 구현하기 위해 like_users라는 ManyToManyField가 있음
Product 모델
from django.db import models
from django.contrib.auth.models import User
class Product(models.Model):
name = models.CharField(max_length=100)
views = models.PositiveIntegerField(default=0) # 조회수
like_users = models.ManyToManyField(User, related_name="liked_products") # 찜한 유저
3. annotate로 찜 수 집계하기
from django.db.models import Count
products = Product.objects.annotate(like_count=Count('like_users'))
annotate()를 통해 like_users 수를 like_count라는 필드로 추가
like_count는 각 Product 객체에 동적으로 생성되는 필드
이 필드는 이후 정렬, 필터링 등에서 사용 가능함
4. annotate와 정렬 결합하기
집계된 필드를 기준으로 정렬할 수 있음
조회수(views)와 찜 수(like_count)를 기준으로 정렬하려는 경우 다음과 같이 구현
from django.db.models import Count
# annotate로 like_count 계산 후 정렬
products = Product.objects.annotate(
like_count=Count('like_users')
).order_by('-views', '-like_count')
annotate()로 like_users 수를 like_count에 저장
order_by()를 사용해 조회수(-views)를 기준으로 정렬
조회수가 같다면 찜 수(-like_count)를 기준으로 정렬
5. 뷰 함수에서 annotate 사용하기
상품 목록 페이지에서 정렬 기준을 선택하는 예시임
사용자가 선택한 정렬 기준에 따라 정렬 순서를 바꿔줌
views.py
from django.shortcuts import render
from django.db.models import Count
from .models import Product
def products(request):
# annotate로 찜 수 계산
products = Product.objects.annotate(like_count=Count('like_users'))
# 정렬 기준 선택
category_option = request.GET.get('category', 'views') # 기본값: views
if category_option == 'views':
products = products.order_by('-views', '-like_count') # 조회수 → 찜 수
elif category_option == 'like_users':
products = products.order_by('-like_count', '-views') # 찜 수 → 조회수
elif category_option == 'pk':
products = products.order_by('-pk') # 최신순
context = {"products": products}
return render(request, "products/products.html", context)
html
<h1>상품 목록</h1>
<form method="GET">
<select name="category">
<option value="views">조회수</option>
<option value="like_users">찜</option>
<option value="pk">최신순</option>
</select>
<button type="submit">정렬</button>
</form>
<ul>
{% for product in products %}
<li>
{{ product.name }} | 조회수: {{ product.views }} | 찜: {{ product.like_count }}
</li>
{% endfor %}
</ul>
6. annotate 사용 주의사항
ManyToManyField를 썼을 대,
annotate()를 사용해 ManyToMany 관계를 집계하면 JOIN이 발생할 수 있음
이로 인해 중복된 레코드가 반환될 수 있으므로 distinct()를 사용하는 것이 좋음