-
[DJANGO] ORM : Aggregation, Annotate코딩/🟢 DJANGO 2021. 11. 10. 20:34
Aggregation : 집합
Annotate : 주석을 달다.
기업협업 프로젝트 기간 Django ORM을 통해 데이터 베이스에서 일차원적으로 나타난 데이터를 가져오는 것이 아니라,
데이터를 가공해서 원하는 값을 찾아야하는 챌린지가 발생했다. 🚨
물론 쿼리로 직접 처리하는 것이 데이터 효율면에서 뛰어날지 모르나,
쿼리에 대한 이해와 함께 장고 ORM에서 데이터를 어떻게 처리하는지 중요하다.
우선 테이블을 살펴보자.
User id bigint email varchar(300) appname char(10) register_datatime datetime social_type varchar(32) country varchar(32) phonenum varchar(30) - 앱별 가입자 수
- 월별 가입자 수
- 월별 가입자 비율
- 가입타입별 가입자 수
앱별 가입자 수 Django ORM
⛔️ 비효율적인 ORM
# #1. 앱별 가입자 수 for app in appname: user_num = User.objects.filter(appname = app) user_count = len(user_num) user_total = len(User.objects.all()) user_ratio = round((user_count/user_total)*100) if app == "": app = "Undefined" result["User_App"].append( { "appname" : app, "user_count" : user_count } )
우선 통신이 가능하게끔 만들었지만 appname이 많아질때마다 데이터베이스에서 조회하는 요청도 많아지기때문에 좋지 못한 코드이다.
어떻게 하면 데이터베이스에 보다 효율적으로 통신할 수 있을까? 🤔
annotate, values
이때 필요한 것은 valuse, annotate 라는 개념이다.
valuse는 그룹화시키고, annotate는 가상의 칼럼값을 추가한다고 생각하고 공식문서를 살펴보자.
Aggregation | Django documentation | Django
Django The web framework for perfectionists with deadlines. Overview Download Documentation News Community Code Issues About ♥ Donate
docs.djangoproject.com
Author, Publisher, Book, Store 테이블로 구성된 데이터베이스를 예시로 들고있다.
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) 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, on_delete=models.CASCADE) pubdate = models.DateField() class Store(models.Model): name = models.CharField(max_length=300) books = models.ManyToManyField(Book)
# Each publisher, each with a count of books as a "num_books" attribute. >>> from django.db.models import Count >>> pubs = Publisher.objects.annotate(num_books=Count('book')) >>> pubs <QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]> >>> pubs[0].num_books 73
🙋♂️각 출판사별로 출판한 책의 개수를 구하고 싶다면?
Publisher에 가상의 칼럼값을 annotate로 만들어서 각 출판한 책을 Count 해주면 된다.
따라서 Publisher: BaloneyPress가 출판한 책은 73개로 나타난다.
근데 이때 문제점이 있다. BalaoneyPress를 위치순서로 알아야한다는 점이다. 만약 위치순서를 모른다면 일일이 세서 접근해야한다는 점이다. 이때 필요한 것은 values로 칼럼값을 지정해서 칼럼값으로 그룹화시킬 수 있다.
apps = User.objects.values('appname').annotate(count_app=Count('appname')) for app in apps: appname = app['appname'] count_app = app['count_app'] result["User_App"].append( { "appname" : appname, "user_count" : count_app } )
월별 가입자 수 Django ORM
Datatime에는 월-일-시간까지 나와있어 이를 월별로 그룹시키기 위해선 Trucate function을 사용해보자.
Truncates a date up to a significant component.
When you only care if something happened in a particular year, hour, or day, but not the exact second, then Trunc (and its subclasses) can be useful to filter or aggregate your data. For example, you can use Trunc to calculate the number of sales per day.>>> from datetime import datetime >>> from django.db.models import Count >>> from django.db.models.functions import TruncMonth, TruncYear >>> from django.utils import timezone >>> start1 = datetime(2014, 6, 15, 14, 30, 50, 321, tzinfo=timezone.utc) >>> start2 = datetime(2015, 6, 15, 14, 40, 2, 123, tzinfo=timezone.utc) >>> start3 = datetime(2015, 12, 31, 17, 5, 27, 999, tzinfo=timezone.utc) >>> Experiment.objects.create(start_datetime=start1, start_date=start1.date()) >>> Experiment.objects.create(start_datetime=start2, start_date=start2.date()) >>> Experiment.objects.create(start_datetime=start3, start_date=start3.date()) >>> experiments_per_year = Experiment.objects.annotate( ... year=TruncYear('start_date')).values('year').annotate( ... experiments=Count('id')) >>> for exp in experiments_per_year: ... print(exp['year'], exp['experiments']) ... 2014-01-01 1 2015-01-01 2
이를 토대로 월별 가입자 수를 구하기 위한 ORM을 만들어보자.
count_group_month = User.objects.annotate(month=TruncMonth('register_datetime')).values('month').annotate(count_monthly_users=Count('id_trv_user')) for register_user in register_users: year_month = str(register_user['month']).split('-')[:2] count_user = register_user['count_monthly_users'] result["Monthly_User"].append( { "year_month" : year_month, "monthly_num" : count_user, } )
참고문헌
Aggregation, Annotate에 대해서
Aggregation | Django documentation | Django
Django The web framework for perfectionists with deadlines. Overview Download Documentation News Community Code Issues About ♥ Donate
docs.djangoproject.com
Truncate에 대해서
#26649 (Group by date_trunc (day, month, year) without extra) – Django
This seems like a regression resulting from the deprecation of .extra. Grouping things by different time ranges is relevant to many django users. It seems this cannot be done without the use of extra right now. The closest I got was Sales.objects.datetimes
code.djangoproject.com
Database Functions | Django documentation | Django
Django The web framework for perfectionists with deadlines. Overview Download Documentation News Community Code Issues About ♥ Donate
docs.djangoproject.com
'코딩 > 🟢 DJANGO' 카테고리의 다른 글
[Django REST framework] 튜토리얼 2 : 요청 및 응답 (0) 2022.02.26 [Django REST framework] 튜토리얼 1 : Serializer (0) 2022.02.25 [DJANGO] 카카오 소셜로그인 구현 (0) 2021.10.03 [DJANGO] 인증과 인가(4) : Login decorator (0) 2021.09.25 [LAFESTA] 수정사항들 (0) 2021.09.13