# Django Aggregation

ag·gre·ga·tion [æ̀ɡriɡéiʃən] 명사

  1. [U] 집합, 집성(集成), 집적(集積)

# 예제로 쓸 모델

from django.db import models

class Author(models.Model): # 작가
    name = models.CharField(max_length=100)
    age = models.IntegerField()

class Publisher(models.Model): # 출판사
    name = models.CharField(max_length=300)
    num_awards = models.IntegerField()

class Book(models.Model): # 책
    name = models.CharField(max_length=300)
    pages = models.IntegerField()
    price = models.DecimalField(max_digits=10, decimal_places=2)
    rating = models.FloatField()
    authors = models.ManyToManyField(Author) # 다수 작가
    publisher = models.ForeignKey(Publisher) # 한 출판사
    pubdate = models.DateField()

class Store(models.Model): # 상점
    name = models.CharField(max_length=300)
    books = models.ManyToManyField(Book) # 다수 책
    registered_users = models.PositiveIntegerField()

# Cheat Sheet

# 책 다 세기
>>> Book.objects.count()
2452

# 출판사가 지앤선인 책들 세기
>>> Book.objects.filter(publisher__name='지앤선').count()
73

# 모든 책들의 가격 평균
>>> from django.db.models import Avg
>>> Book.objects.all().aggregate(Avg('price'))
{'price__avg': 34.35}

# 모든 책들 중 가장 비싼 가격
>>> Book.objects.all().aggregate(Max('price'))
{'price__max':  Decimal('81.20')}

# 'price_per_page'란 이름으로, 장당 가격을 구한다.
>>> Book.objects.all().aggregate(
...    price_per_page=Sum(F('price')/F('pages'), output_field=FloatField()))
{'price_per_page': 0.4470664529184653}

# All the following queries involve traversing the Book<->Publisher
# foreign key relationship backwards.

# 출판사마다 reverse해서 book을 가져오고, 그거 갯수 샌다.
>>> pubs = Publisher.objects.annotate(num_books=Count('book'))
>>> pubs
[<Publisher BaloneyPress>, <Publisher SalamiPress>, ...]
>>> pubs[0].num_books
73

# 출판사 reverse book 갯수가 많은 탑 5개
# The top 5 publishers, in order by number of books.
>>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5]
>>> pubs[0].num_books
1323

# 쿼리셋에 aggreates하기

장고에선 두 가지 방식으로 aggreates를 만들 수 있다.

# 1. 전체 쿼리셋에 Summary values 만들기

e.g. 모든 책에 대한 평균 가격 구하기

>>> Book.objects.aggregate(Avg('price'))
{'price__avg': 34.35}

>>> Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 34.35} # 이름 명시

>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
{'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')} # 다중 반환

aggreate()는 Queryset의 Terminal clause로, 키-밸류 쌍을 반환한다.

# 2. 쿼리셋 각 요소에 aggregates하기

e.g. 책 리스트가 있을 때, 각 책마다 저자가 몇 명 있는지 계산. 이는 annotate()으로 만들 수 있다. aggreate()랑은 다르게 terminal clause가 아니고 QuerySet을 반환한다. Book과 Authors는 M2M 관계이다.

# 책마다 저자가 몇 명 있는지 계산된 책 리스트 반환
>>> q = Book.objects.annotate(Count('authors'))

>>> q[0]
<Book: The Definitive Guide to Django>
>>> q[0].authors__count
2

# 이름 지어주기
>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2

# Joins and aggregates

Store마다 책들의 min, max 가격 표기 Store에 M2M으로 연결되어있는 books를 찾아, books모델의 price를 비교. 더블 언더스코어로 관계된 모델을 찾아갈 수 있다.

>>> Store.objects.annotate(min_price=Min('books__price'), max_price=Max('books__price'))

# 역참조

Publisher엔 Book이 거꾸로 Foriegn key로 연결되어있다. Lowercase로 역참조를 할 수 있다.

>>> Publisher.objects.annotate(Count('book'))

# Refer

https://docs.djangoproject.com/en/1.9/topics/db/aggregation/ http://raccoonyy.github.io/conditional-annotate-with-django-query/ http://raccoonyy.github.io/django-annotate-and-aggregate-like-as-excel/