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은 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)...']>
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로 갖는 딕셔너리를 반환한다.
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)이 붙은 것을 확인할 수 있다.