[Django] Annotate / Aggregate

송진수·2021년 8월 12일
2
class SubCategory(models.Model):
    category     = models.ForeignKey('Category', on_delete=models.SET_NULL, null=True)
    sub_category = models.CharField(max_length=45)
    
class Product(models.Model):
    sub_category     = models.ForeignKey('SubCategory', on_delete=models.SET_NULL, null=True)
    color            = models.ForeignKey('Color', on_delete=models.SET_NULL, null=True, related_name='products')
    name             = models.CharField(max_length=45)
    price            = models.DecimalField(max_digits=10, decimal_places=2)
    style_code       = models.CharField(max_length=45)
    origin           = models.CharField(max_length=45)
    manufacture_date = models.DateField()
    description      = models.TextField()
    image_url        = models.CharField(max_length=2000)
    group            = models.CharField(max_length=45)

Annotation

Annotation은 Django 쿼리셋의 각각의 인스턴스에 대해 필드와 필드값을 생성해준다. 마치 쿼리셋으로 이루어진 가상의 테이블에 가상의 열을 추가하는 것과 같은 효과이다.

각 상품의 옷 종류 이름은 Product 테이블에는 없는 필드여서 각 인스턴스에서 FK를 SubCategory 테이블을 통해 참조해야 하지만, annotate를 활용하여 subcategory_name이라는 필드명으로 Product 쿼리셋에 붙여보자.

from django.db.models import F
from products.models import Product

# all()은 생략 가능, 'FK필드__필드명' 쿼리로 참조할 테이블의 필드에 접근, F객체를 사용하여 접근한 필드의 값을 가져옴
products = Product.objects.all().annotate(subcategory_name=F('sub_category__sub_category'))

products.values('subcategory_name')
# <QuerySet [{'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '반팔'}, {'subcategory_name': '반팔'}, {'subcategory_name': '반팔'}, {'subcategory_name': '반팔'}, {'subcategory_name': '반팔'}, {'subcategory_name': '반팔'}, {'subcategory_name': '반팔'}, {'subcategory_name': '반팔'}, '...(remaining elements truncated)...']>

원래 테이블에 있던 필드처럼 반복문을 돌려 인스턴스의 속성으로도 호출할 수 있고, annotate한 열을 기준으로 쿼리셋을 정렬하는 것도 가능하다.

for product in products:
    print(product.subcategory_name, end=' ')
    
# 긴팔 긴팔 긴팔 긴팔 긴팔 긴팔 긴팔 긴팔 긴팔 긴팔 긴팔 긴팔 
# 반팔 반팔 반팔 반팔 반팔 반팔 반팔 반팔 반팔 반팔 반팔 반팔 
# 긴바지 긴바지 긴바지 긴바지 긴바지 긴바지 긴바지 긴바지 긴바지 긴바지 
# 반바지 반바지......

p1 = products.order_by('subcategory_name')

p1.values('subcategory_name')

# <QuerySet [{'subcategory_name': '긴바지'}, {'subcategory_name': '긴바지'}, {'subcategory_name': '긴바지'}, {'subcategory_name': '긴바지'}, {'subcategory_name': '긴바지'}, {'subcategory_name': '긴바지'}, {'subcategory_name': '긴바지'}, {'subcategory_name': '긴바지'}, {'subcategory_name': '긴바지'}, {'subcategory_name': '긴바지'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, {'subcategory_name': '긴팔'}, '...(remaining elements truncated)...']>

Aggregation

Annotation이 쿼리셋의 인스턴스 하나하나에 영향을 미친다면, Aggregation은 쿼리셋의 모든 인스턴스의 특정 필드값을 대상으로 작동한다.

이를 위한 Aggregation Function으로 Max, Min, Sum, Count 등을 불러와 사용할 수 있다.

from django.db.models import Max, Sum

# 상품 테이블의 모든 데이터의 price 필드의 값의 합
a = Product.objects.aggregate(Sum('price'))

a
# {'price__sum': Decimal('16029273.00')}

b = Product.objects.aggregate(max_price=Max('price'))

b
# {'max_price': Decimal('248688.00')}

모델의 aggregate 메서드는 위와 같이 지정한 이름, 혹은 미지정시 django 자체 기본 이름을 Key로, Aggregation Function의 결과를 Value로 갖는 딕셔너리를 반환한다.

응용

#1

Annotation과 Aggregation을 둘다 활용하여, ProductOption 테이블을 통해 각 상품의 사이즈별 재고 데이터를 합한 값(상품별 총 재고)을 Product 쿼리셋에 붙여보자.

products = Product.objects.annotate(stock=Sum('productoption__stock'))

products.values('id','stock')
# <QuerySet [{'id': 1, 'stock': 25}, {'id': 2, 'stock': 9}, {'id': 3, 'stock': 8}, {'id': 4, 'stock': 12}, {'id': 5, 'stock': 9}, {'id': 6, 'stock': 12}, {'id': 7, 'stock': 19}, {'id': 8, 'stock': 11}, {'id': 9, 'stock': 17}, {'id': 10, 'stock': 13}, {'id': 11, 'stock': 9}, {'id': 12, 'stock': 15}, {'id': 13, 'stock': 21}, {'id': 14, 'stock': 11}, {'id': 15, 'stock': 10}, {'id': 16, 'stock': 14}, {'id': 17, 'stock': 18}, {'id': 18, 'stock': 11}, {'id': 19, 'stock': 25}, {'id': 20, 'stock': 12}, '...(remaining elements truncated)...']>

각 상품 id에 해당하는 재고 데이터 열(stock)이 붙은 것을 확인할 수 있다.

profile
보초

0개의 댓글