오히려 좋아..

상황이 나쁘게만 흘러가는 것 같을 때 외쳐보자.. .

궁금한 마음으로 포트폴리오 보기

Web Programming/Django

[Django] Aggregate와 Annotate

junha6316 2021. 7. 14. 14:17

아래에서 사용된 코드는 아래 레포지토리에서 받을 수 있다.

https://github.com/junha6316/django_ex

 

junha6316/django_ex

Contribute to junha6316/django_ex development by creating an account on GitHub.

github.com

 

장고 ORM의 기능 중 Aggregate와 Annotate에 대해 정리한 글이다. 어떤 기능을 하고 있는지는 많이 들어 알고 있지만 실제로 사용해본적은 없다;

http://raccoonyy.github.io/django-annotate-and-aggregate-like-as-excel/

 

(엑셀만큼 쉬운) Django Annotation/Aggregation

Django ORM을 강력하게 만들어주는 기능 중 하나는 바로 애너테이션(annotate)과 애그리게이션(aggregate)입니다. 이 두 기능이 명쾌하게 와닿지 않아서 사용하지 못하다가, 엑셀에 빗대어 이해해보니

raccoonyy.github.io

아래 블로그를 참고해 작성했다.

 

이런 상황을 가정해보자

 

준하네 마트에는 모든 것을 판다. 그리고 초울트라특급 기술이 적용된 자동 판매 인식기 덕분에 언제 어떤 물건이 팔렸는지에 대한 로그가 데이터 베이스에 자동으로 저장된다. 하지만 이 마트에는 없는게 하나 있는데 바로 엑셀이다. 그덕에 데이터는 많지만 어떤 날에 어떤 물건이 얼마나 팔렸는지 매출이 얼마인지 알기 위해서 준하는 컴퓨터 화면을 보면서 24시간 주판을 두드려 계산했다. 주판을 두드리던 준하앞에 제이미 폭스가 쌍권총을 들고 나타나 이 문제를 해결해주겠다고 한다. 제이미가 어떻게 이 문제를 해결하는지 보자

제이미 폭스는 장고:분노의 추적자에서 장고 역할을 맡았다.

사용한 모델은 Product와 OrderLog이며 아래와 같다. OrderLog는 판매데이터로 product를 외래키로 참조한다.

class Product(TimeStampedModel):
	
    name = models.CharField("이름", max_length=150, unique=True)
    price = models.IntegerField("가격")

    def __str__(self):
        return self.name


class OrderLog(models.Model):
    
    created = models.DateTimeField()
    product = models.ForeignKey('products.Product', related_name='order_log', on_delete=models.CASCADE)

1. 데이터 불러오기

먼저 제이미는 필요한 데이터만 가져오는 방법을 알려줬다.

"데이터는 아래처럼 불러올 수 있고 Queryset객체를 반환한다. QuerySet객체는 iterate하는 시점에서 딕셔너리로 반환된다.

물론 데이터를 불러올 수 있는 다양한 방법이 있지만 필요하지 않은 데이터를 갖고오거나 불필요한 쿼리를 날리는 것은 지양해야한다.

참고로  외래키로 참조되어 있는 필드를 불러오기 위해선 더블언더바(__)를 사용하면된다. OrderLog의 외래키 관계에 있는 product의 name을 갖고 오기 위해선 product__name로 적어서 갖고 오면된다. 장고"

 

logs = OrderLog.object.values(
   'created', 'product__name', 'product__price'
 )
 #외래키로 참조되어 있는 필드를 불러오기 위해선 더블언더바(__)를 사용하면된다.
 #order_log의 외래키 관계에 있는 product의 name을 갖고 오기 위해선 product__name
 

for log in logs:
    #print(log.created) 이렇게 안됨
    print(log['created']

위의 결과는 아래와 같다.

딕셔너리로 나오는 것을 확인할 수 있다.
준하 이래도 아직도 모르겠어?

2. Annotate

준하는 제이미가 보여준 데이터를 보면서 너무 지저분하다고 생각했다. 그래서 물어봤다. 제이미 이거 데이터 좀 깨끗하게 못하나?  제이미는 쌍권총을 관자놀이에 대고 말했다.

"결과를 보면 외래키로 참조된 product__ 가 계속 나오는 것을 볼 수 있다. 이렇게 출력된다면 매번 log에서 어떤 값을 사용할 때마다 product__를 사용해야한다. 이건 불합리하다. 장고에서는 Annotate 기능을 이용하면 해결할 수 있다. 알겠니? 장고 "

from django.db.models import F

logs = OrderLog.objects.annotate(
   name=F("product__name"),
   price=F("product__price")
   ).values(
   'created', 'name', 'price'
   )
   
for log in logs:
    print(log)

한결 깔끔하게 정리된걸 볼 수 있다.

사실 Annotate는 단순히 이름만 바꾸는 기능은 아니다. 이 부분은 뒤쪽 응용 부분에서 다루도록 하겠다.

이제 알겠냐고

3. Aggregate

제이미가 말했다. 

"위에서 데이터를 가져왔다면 이제 이 데이터를 이용해 원하는 수치를 얻을 수 있다. 먼저 전체 판매 데이터의 총합을 알아보겠다. 총합은 aggregate를 사용하면된다. 알겠냐 장고?"

from django.db.models import Sum


#위에서 annotate된 QuerySet을 사용하면된다.
logs.aggregate(total_price=Sum('price'))
>>> {'total_price': 488000}

이번에는 날짜별 합을 가져와보자

from django.db.models import Sum


#위에서 annotate된 QuerySet을 사용하면된다.
daily_sum_list = logs.values(
	'created'
    ). annotate(
	daily_sum = Sum('price')     
)


for data in daily_sum_list:
    print(data)

짜란~

다음은 날짜별로 어떤 제품이 몇개 팔렸는지 알아보자. 약간 Group By 느낌이라고 생각하면된다.

from django.db.models import Count


#위에서 annotate된 QuerySet을 사용하면된다.
product_cnt_list = logs.values(
	'created','name'
    ). annotate(
	product_cnt = Count('name')     
)


for data in product_cnt_list:
    print(data)

짜란~

위 두개의 예제를 통해 values로 먼저 가져오고 annotate를 이용하면 된다는 것을 알 수 있다.

준하 마지막으로 특정 제품의 날짜별 판매 수량을 가져오려면 어떻게 해야할까?  

from django.db.models import Count


#위에서 annotate된 QuerySet을 사용하면된다.
coupang_daily_cnt_list = qs.filter(
     name="쿠팡"
     ).values(
	'created','name'
    ). annotate(
	product_cnt = Count('name')     
)


for data in coupang_daily_cnt_list:
    print(data)

짜짜란~
모르겠으면 다시 올라가서 봐

제이미 덕에 준하는 더 효율적으로 데이터를 사용할 수 있게 되었고 부자가 되었다. 

준하 나 간다~