.annotate()
의 각 인수는 QuerySet의 각 객체에 추가 될 주석이다. 주석들은 단순한 값, 모델의 필드에 대한 참조, QuerySet의 각 객체와 관련된 집계(averages, sums 등)이다. 새로운 요소를 데이터 쿼리에 추가하고 해당 요소를 따로 출력해볼 수 있도록 해주는 것인데, 데이터가 몇 개 존재하는지를 .count()
라는 함수를 이용해서 annotate
해주고 이를 __count로 접근할 수 있다.
쉽게 말해서 .annotate()
는 필드 하나를 만들고 거기에 '어떤 내용' 을 채우게 만드는 것이다. 엑셀에서 컬럼 하나를 만드는 것과 같다고 보면 된다. 내용에는 1. 다른 필드의 값을 그대로 복사하거나, 2. 다른 필드의 값들을 조합한 값을 넣을 수 있다.
annotate의 사전적 의미는 '주석을 달다'인데, Django에서는 주석 대신 '필드'를 추가한다고 생각하면 편하다. (엑셀이라면 컬럼을 추가하는 셈.)
>>> from django.db.models import Count
>>> q = Blog.objects.annotate(Count('entry'))
>>> q[0].name # 첫 블로그의 이름
'Blogasaurus'
>>> q[0].entry__count # 첫 블로그의 항목 수
42
# Blog에서 Count함수를 이용하여 entry가 몇개 존재하는지를 annotate해준다.
# 특별히 alias를 지정하지 않는 한 __count로 접근한다.
# 키워드 인수를 사용하여 집계 함수를 지정함으로 주석의 이름을 제어할 수 있다.
>>> q = Blog.objects.annotate(number_or_entries=Count('entry'))
>>> p[0].number_or_entries
42
# number_of_entries라는 alias를 지정하면 __count 대신에
# .number_of_entries로 접근할 수 있다.
QuerySet에 대해 계산된 집계를 dict로 반환한다. 집계도 쿼리식이기 때문에 다른 집계나 값과 결합하여 복잡한 집계를 만들 수 있다. 키워드 인수를 사용해서 집계된 집계는 키를 주석의 이름으로 사용한다. 익명 인수는 집계함수의 이름과 집계되는 모델필드에 따라 이름이 생성된다. 복합집계는 익명 인수를 사용할 수 없으므로 별칭을 지정해줘야한다.
대부분의 상황에서 자동생성된 키는 접근하기 불편하므로 키워드 인자를 건네주는 습관을 들이자.
집계함수란?
테이블의 여러 행이나 전체 행으로부터 하나의 값을 집계하여 반환하는 함수이다.
COUNT, SUM, AVG, MAX, MIN 등이 있다.
# 블로그 항목을 제공한 작성자의 수를 조회
>>> from django.db.models import Count
>>> q = Blog.objects.aggregate(Count('entry'))
{'entry__count': 16}
# 키워드 인수를 사용하여 집계 함수를 지정(리턴되는 집계 값의 이름을 제어)
>>> q = Blog.objects.aggregate(number_of_entries=Count('entry'))
{'number_of_entries': 16}
Django에서 쿼리셋의 특정 필드를 모두 더하거나 평균, 개수 등을 구할 때 .aggregate()
를 쓴다.
aggregate의 사전적 의미는 '합계', '종합'이다. Django에서는 필드 전체의 합이나 평균, 개수 등을 계산할 때 사용한다. (엑셀에서는 한 컬럼 전체의 합이나 평균, 개수를 계산한다고 보면 된다.)
결과값의 데이터 타입을 결정하거나 서브쿼리 사이의 계산도 가능하다.
Product.objects.filter(menu__id=2).aggregate(kcal_diff=Max('nutrient__kcal') - Min('nutrient__kcal'))
SELECT CAST((CAST(MAX("nutrients"."kcal") AS NUMERIC) -
CAST(MIN("nutrients"."kcal") AS NUMERIC)) AS NUMERIC)
AS "kcal_diff" FROM "products" LEFT OUTER JOIN "nutrients"
ON ("products"."id" = "nutrients"."product_id")
WHERE "products"."menu_id" = 2;
{'kcal_diff': Decimal('555')}
aggregate
는 전체 쿼리셋에 대한 값을 계산하고, annotate
는 쿼리셋의 각 객체들에 적용된다..
>>> Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 34.35}
# 쿼리셋에 있는 모든 책의 평균 가격이 포함된 dict를 반환한다.
>>> q = Book.objects.annotate(num_authors=Count('authors'))
>>> q[0].num_authors
2
>>> q[1].num_authors
1
# q는 책의 쿼리셋이지만, 각 책에는 저자수로 주석이 달렸다.
https://simpleisbetterthancomplex.com/tutorial/2016/12/06/how-to-create-group-by-queries.html
파이썬 쉘을 실행하고 테스트를 해보자.
from population.models import *
from django.db.models import Avg, Min, Max, Count, F, Sum
1-1. 인구수 총합 구하기
>>> City.objects.aggregate(Sum('population'))
2020-05-06 00:52:30,190 DEBUG (0.000) SELECT SUM("cities"."population") AS "population__sum" FROM "cities"; args=()
{'population__sum': 992633710}
1-2. 인구수 평균 구하기
>>> City.objects.aggregate(Avg('population'))
2020-05-06 00:53:35,299 DEBUG (0.000) SELECT AVG("cities"."population") AS "population__avg" FROM "cities"; args=()
{'population__avg': 11678043.647058824}
1-3. 다음 두 쿼리의 차이점은?
>>> a = City.objects.aggregate(Sum('population'))
>>> a
2020-05-06 00:54:22,512 DEBUG (0.000) SELECT SUM("cities"."population") AS "population__sum" FROM "cities"; args=()
{'population__sum': 992633710}
>>> type(a)
<class 'dict'>
>>> dir(a)
['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
>>> b = City.objects.values('country__name').annotate(Sum('population'))
>>> b
2020-05-06 00:54:50,364 DEBUG (0.000) SELECT "countries"."name", SUM("cities"."population") AS "population__sum" FROM "cities" INNER JOIN "countries" ON ("cities"."country_id" = "countries"."id") GROUP BY "countries"."name" LIMIT 21; args=()
<QuerySet [{'country__name': 'Angola', 'population__sum': 6542944}, {'country__name': 'Argentina', 'population__sum': 13074000}, {'country__name': 'Australia', 'population__sum': 9650500}, {'country__name': 'Bangladesh', 'population__sum': 17151925}, {'country__name': 'Brazil', 'population__sum': 38676123}, {'country__name': 'Canada', 'population__sum': 6055724}, {'country__name': 'Chile', 'population__sum': 6683852}, {'country__name': 'China', 'population__sum': 309898600}, {'country__name': 'Colombia', 'population__sum': 7878783}, {'country__name': 'France', 'population__sum': 12405426}, {'country__name': 'Germany', 'population__sum': 31978954}, {'country__name': 'India', 'population__sum': 122104088}, {'country__name': 'Indonesia', 'population__sum': 30000000}, {'country__name': 'Iran', 'population__sum': 14595904}, {'country__name': 'Italy', 'population__sum': 8589190}, {'country__name': 'Japan', 'population__sum': 65372000}, {'country__name': 'Mexico', 'population__sum': 30613445}, {'country__name': 'Nigeria', 'population__sum': 21000000}, {'country__name': 'Pakistan', 'population__sum': 24300000}, {'country__name': 'Peru', 'population__sum': 9886647}, '...(remaining elements truncated)...']>
>>> type(b)
<class 'django.db.models.query.QuerySet'>
>>> dir(b)
['__and__', '__bool__', '__class__', '__deepcopy__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__or__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_add_hints', '_batched_insert', '_chain', '_clone', '_combinator_query', '_create_object_from_params', '_db', '_earliest', '_extract_model_params', '_fetch_all', '_fields', '_filter_or_exclude', '_for_write', '_has_filters', '_hints', '_insert', '_iterable_class', '_iterator', '_known_related_objects', '_merge_known_related_objects', '_merge_sanity_check', '_next_is_sticky', '_not_support_combined_queries', '_populate_pk_values', '_prefetch_done', '_prefetch_related_lookups', '_prefetch_related_objects', '_raw_delete', '_result_cache', '_sticky_filter', '_update', '_validate_values_are_expressions', '_values', 'aggregate', 'all', 'annotate', 'as_manager', 'bulk_create', 'bulk_update', 'complex_filter', 'count', 'create', 'dates', 'datetimes', 'db', 'defer', 'delete', 'difference', 'distinct', 'earliest', 'exclude', 'exists', 'explain', 'extra', 'filter', 'first', 'get', 'get_or_create', 'in_bulk', 'intersection', 'iterator', 'last', 'latest', 'model', 'none', 'only', 'order_by', 'ordered', 'prefetch_related', 'query', 'raw', 'resolve_expression', 'reverse', 'select_for_update', 'select_related', 'union', 'update', 'update_or_create', 'using', 'values', 'values_list']
>>> order_qs = Order.objects.values('created_at','product__name','product__price')
>>> order_qs
2020-05-06 02:04:27,838 DEBUG (0.000) SELECT "orders"."created_at", "products"."name", "products"."price" FROM "orders" INNER JOIN "products" ON ("orders"."product_id" = "products"."id") LIMIT 21; args=()
<QuerySet [{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'product__name': 'iPad', 'product__price': 150000}, {'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'product__name': 'macbook', 'product__price': 3000000}, {'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'product__name': 'iPhone', 'product__price': 800000}, {'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'product__name': 'macbook', 'product__price': 3000000}, {'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'product__name': 'iPad', 'product__price': 150000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'iPad', 'product__price': 150000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'macbook', 'product__price': 3000000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'iPhone', 'product__price': 800000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'iPad', 'product__price': 150000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'macbook', 'product__price': 3000000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'iPhone', 'product__price': 800000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'iPad', 'product__price': 150000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'iPad', 'product__price': 150000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'macbook', 'product__price': 3000000}, {'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'product__name': 'macbook', 'product__price': 3000000}, {'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'product__name': 'iPhone', 'product__price': 800000}, {'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'product__name': 'iPhone', 'product__price': 800000}]>
>>> for order in order_qs:
... print(order)
...
2020-05-06 02:05:03,242 DEBUG (0.000) SELECT "orders"."created_at", "products"."name", "products"."price" FROM "orders" INNER JOIN "products" ON ("orders"."product_id" = "products"."id"); args=()
{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'product__name': 'iPad', 'product__price': 150000}
{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'product__name': 'macbook', 'product__price': 3000000}
{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'product__name': 'iPhone', 'product__price': 800000}
{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'product__name': 'macbook', 'product__price': 3000000}
{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'product__name': 'iPad', 'product__price': 150000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'iPad', 'product__price': 150000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'macbook', 'product__price': 3000000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'iPhone', 'product__price': 800000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'iPad', 'product__price': 150000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'macbook', 'product__price': 3000000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'iPhone', 'product__price': 800000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'iPad', 'product__price': 150000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'iPad', 'product__price': 150000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'product__name': 'macbook', 'product__price': 3000000}
{'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'product__name': 'macbook', 'product__price': 3000000}
{'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'product__name': 'iPhone', 'product__price': 800000}
{'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'product__name': 'iPhone', 'product__price': 800000}
>>> type(order_qs)
<class 'django.db.models.query.QuerySet'>
>>> order_qss = Order.objects.annotate( name =F('product__name'), price = F('product__price')).values('created_at', 'name', 'price')
>>> order_qss
2020-05-06 02:06:42,852 DEBUG (0.000) SELECT "orders"."created_at", "products"."name" AS "name", "products"."price" AS "price" FROM "orders" INNER JOIN "products" ON ("orders"."product_id" = "products"."id") LIMIT 21; args=()
<QuerySet [{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'name': 'iPad', 'price': 150000}, {'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'price': 3000000}, {'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'name': 'iPhone', 'price': 800000}, {'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'price': 3000000}, {'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'name': 'iPad', 'price': 150000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'iPad', 'price': 150000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'price': 3000000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'iPhone', 'price': 800000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'iPad', 'price': 150000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'price': 3000000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'iPhone', 'price': 800000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'iPad', 'price': 150000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'iPad', 'price': 150000}, {'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'price': 3000000}, {'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'price': 3000000}, {'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'name': 'iPhone', 'price': 800000}, {'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'name': 'iPhone', 'price': 800000}]>
>>> for order in order_qss:
... print(order)
...
2020-05-06 02:08:15,409 DEBUG (0.000) SELECT "orders"."created_at", "products"."name" AS "name", "products"."price" AS "price" FROM "orders" INNER JOIN "products" ON ("orders"."product_id" = "products"."id"); args=()
{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'name': 'iPad', 'price': 150000}
{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'price': 3000000}
{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'name': 'iPhone', 'price': 800000}
{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'price': 3000000}
{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'name': 'iPad', 'price': 150000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'iPad', 'price': 150000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'price': 3000000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'iPhone', 'price': 800000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'iPad', 'price': 150000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'price': 3000000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'iPhone', 'price': 800000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'iPad', 'price': 150000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'iPad', 'price': 150000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'price': 3000000}
{'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'price': 3000000}
{'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'name': 'iPhone', 'price': 800000}
{'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'name': 'iPhone', 'price': 800000}
>>> type(order_qss)
<class 'django.db.models.query.QuerySet'>
>>> order_qss.aggregate(total_price=Sum('price'))
2020-05-06 02:54:54,859 DEBUG (0.000) SELECT SUM("price") FROM (SELECT "orders"."created_at" AS Col1, "products"."name" AS "name", "products"."price" AS "price" FROM "orders" INNER JOIN "products" ON ("orders"."product_id" = "products"."id")) subquery; args=()
{'total_price': 22900000}
>>> daily_list = order_qss.values('created_at').annotate(daily_total=Sum('product__price'))
# created_at을 갖고와서 각 객체에 해당하는 합계값을 주석으로 달아준다.
# 4월 10일 객체를 갖고와서 해당하는 날짜의 합계값을 주석으로 달아주고,
# 4월 11일 객체를 갖고와서 해당하는 날짜의 합계값을 주석으로 달아주고..
# 그것들을 쿼리셋으로 갖고있다가 for문으로 풀어낸다.
>>> for dd in daily_list:
... print(dd)
...
2020-05-06 02:56:38,548 DEBUG (0.000) SELECT "orders"."created_at", SUM("products"."price") AS "daily_total" FROM "orders" INNER JOIN "products" ON ("orders"."product_id" = "products"."id") GROUP BY "orders"."created_at"; args=()
{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'daily_total': 7100000}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'daily_total': 11200000}
{'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'daily_total': 4600000}
>>> cc = order_qss.values('created_at','name').annotate(count=Count('name'))
>>> for c in cc:
... print(c)
...
2020-05-06 03:05:59,121 DEBUG (0.000) SELECT "orders"."created_at", "products"."name" AS "name", COUNT("products"."name") AS "count" FROM "orders" INNER JOIN "products" ON ("orders"."product_id" = "products"."id") GROUP BY "orders"."created_at", "products"."name"; args=()
{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'name': 'iPad', 'count': 2}
{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'name': 'iPhone', 'count': 1}
{'created_at': datetime.datetime(2020, 4, 10, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'count': 2}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'iPad', 'count': 4}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'iPhone', 'count': 2}
{'created_at': datetime.datetime(2020, 4, 11, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'count': 3}
{'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'name': 'iPhone', 'count': 2}
{'created_at': datetime.datetime(2020, 4, 12, 0, 0, tzinfo=<UTC>), 'name': 'macbook', 'count': 1}
(엑셀만큼 쉬운) Django Annotation/Aggregation
상세한 테이블 정보나 모델정보는 해당 페이지에서 확인하자.
1. 제품별 가격 참조하기 (values)
Django에서 제품별 가격을 참조하려면 values
메서드를 사용하면 된다.
order_qs = OrderLog.objects.values(
'created', 'product__name', 'product__price'
)
order_queryset
의 내용을 출력하면 엑셀과 같아진다.
>>> for order in order_qs:
... print(order)
...
{'product__price': 9900, 'created': datetime.datetime(2016, 4, 1, 0, 0), 'product__name': 'ABC Activity'}
{'product__price': 8200, 'created': datetime.datetime(2016, 4, 1, 0, 0), 'product__name': '동물동요'}
# (중략)
{'product__price': 9900, 'created': datetime.datetime(2016, 4, 3, 0, 0), 'product__name': 'ABC Activity'}
여기서 밑줄 두 개가 붙은 모습을 짧은 이름으로 바꿔보자.
# 앞의 쿼리를 재작성
order_qs = OrderLog.objects.annotate(
name=F('product__name'),
price=F('product__price')
).values(
'created', 'name', 'price'
)
# 내용을 보면,
{'price': 9900, 'created': datetime.datetime(2016, 4, 1, 0, 0), 'name': 'ABC Activity'}
{'price': 8200, 'created': datetime.datetime(2016, 4, 1, 0, 0), 'name': '동물동요'}
{'price': 38400, 'created': datetime.datetime(2016, 4, 1, 0, 0), 'name': '사운드북 패키지'}
...
# (하략)
2. 제품 가격 모두 더해서 총 판매액 구하기 (aggregate, Sum)
price의 값을 모두 합해서 총 판매액을 구해보자.
Django에서는 쿼리셋의 특정 필드를 모두 더할 때 aggregate
메서드를 사용한다.
# 앞의 코드에서 이어짐.
>>> order_qs.aggregate(total_price=Sum('price'))
{'total_price': 262200}
3. 일별 판매액 구하기 (values, annoate, Sum)
일별 판매액을 구할 때, Django에서는 뭔가를 기준으로 값들을 묶어야하므로 values
와 annotate
를 사용하면 된다. 날짜(created)를 기준으로 하고, 날짜별로 묶고 싶은 값은 가격(price) 이다. 묶은 값들을 모두 더해서 daily_total 필드에 넣어주자.
values
메서드 이전에annoate
로 추가했던 필드는,values
메서드 이후에 나오는annotate
메서드에서 참조할 수 없다. 여기서는 product__price를 그대로 적어줘야한다.
daily_list = order_qs.values(
'created'
).annotate(
daily_total=Sum('product__price')
)
# 내용을 확인해보면,
>>> for data in daily_data_list:
... print(data)
...
{'daily_total': 113000, 'created': datetime.datetime(2016, 4, 1, 0, 0)}
{'daily_total': 84500, 'created': datetime.datetime(2016, 4, 2, 0, 0)}
{'daily_total': 64700, 'created': datetime.datetime(2016, 4, 3, 0, 0)}
4. 날짜별 + 제품별 판매 개수 구하기 (values, annotate, Count)
날짜별로 각 제품이 몇 개씩 팔렸는지 알고 싶다면, values
에 넣을 기준 필드는 created와 name이 되겠다. annotate
에서는 레코드 개수를 세기 위해서 Count
메서드를 사용하자. count 필드를 만들어서 일별 + 제품별 판매개수를 넣어주면 된다.
daily_count = order_qs.values(
'created', 'name'
).annotate(
count=Count('name')
)
# 내용을 확인해보면,
>>> for data in daily_count:
... print(data)
...
{'count': 2, 'created': datetime.datetime(2016, 4, 1, 0, 0), 'name': 'ABC Activity'}
{'count': 2, 'created': datetime.datetime(2016, 4, 1, 0, 0), 'name': '동물동요'}
# (중략)
{'count': 1, 'created': datetime.datetime(2016, 4, 3, 0, 0), 'name': '사운드북 패키지'}
5. 특정 제품의 날짜별 판매 개수 구하기 (filter)
전체 제품이 아닌 특정 제품의 날짜별 판매 개수를 알고 싶을 때는 어떻게 할까?
Django에서는 filter
메서드를 사용하면 된다. filter
외의 부분은 바로 앞의 코드와 동일하다.
sound_book_daily_count = order_qs.filter(
name='ABC Activity'
).values(
'created', 'name'
).annotate(
count=Count('product')
)
# 내용을 확인해보면,
{'name': 'ABC Activity', 'count': 2, 'created': datetime.datetime(2016, 4, 1, 0, 0}
{'name': 'ABC Activity', 'count': 3, 'created': datetime.datetime(2016, 4, 2, 0, 0}
{'name': 'ABC Activity', 'count': `, 'created': datetime.datetime(2016, 4, 3, 0, 0}