안녕하세요.
평소 ORM은 사용할 줄 알지만 내부적으로 쿼리문이 어떻게 짜여지고,
어떻게 사용해야 더 효율적이고, 최대한 쿼리문을 줄일 수 있는 방법을 찾아보면서 코드를 짜다가
휴일에 시간이 생겨 평소에 Github Star를 박아두었던 Django CookBook을 보면서 ORM 사용법을 복습하고
지금보다 더 효율적인 쿼리를 짜기 위해 Cookbook을 살펴보겠습니다.
장고 ORM 요리책 — Django ORM Cookbook 2.0 documentation
© Copyright 2018, Agiliq Revision 159ade05....
django-orm-cookbook-ko.readthedocs.io
"Cookbook이란 장고 ORM 요리책은 장고를 이용한 다양한 레시피(조리법)를 담은 책입니다. 장고 ORM/쿼리셋으로 ~을 하려면 어떻게 하나요? 하는 50여 개의 질문과 답을 담고 있습니다."
Model은 링크를 눌러 README 에 있는 Events와 Entities 를 생성해주시면 됩니다.
python manage.py startapp events
python manage.py startapp entities
App을 생성해주고, README에 있는 코드를 입력해주고, migrations, migrate 해주시면 테이블이 생성됩니다.

테이블 생성 코드의 궁금한 점
1. from django.utils.text import slugify 참고 URL
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='reporter'
)
slug = models.SlugField()
def save(self, *args, **kwargs):
self.slug = slugify(self.headline)
super(Article, self).save(*args, **kwargs)
def __str__(self):
return self.headline
- slug 생성을 위한 모듈이다.
- slugify("Python slug 생성", allow_unicode=True)
- 한글을 넣고싶다면 allow_unicode를 True로 주어야 한다.
- model의 save함수를 overriding하여 model이 저장될 때 마다 slug field에 값을 추가한다
2. editable=False
class Event(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False # 수정 가능 유무
# ex) DateTimeField의 auto_now_add 속성이 True로 설정되면 editable 속성이 자동으로
# False 로 설정되기 때문에 editable 속성을 True로 변경해주면 디테일 화면에서 확인 및 수정이 가능해진다.
)
epic = models.ForeignKey(Epic, on_delete=models.CASCADE)
details = models.TextField()
years_ago = models.PositiveIntegerField()
3. managed = False
class TempUser(models.Model):
first_name = models.CharField(max_length=100)
class Meta:
managed = False # 해당 모델은 자동으로 테이블을 생성하지 않게 된다.
db_table = "temp_user"
CookBook Docs
1. Django ORM이 실행하는 실제 SQL 쿼리문을 확인할 수 있나요?
>>> queryset = Event.objects.all()
>>> str(queryset.query)
SELECT "events_event"."id", "events_event"."epic_id",
"events_event"."details", "events_event"."years_ago"
FROM "events_event"
아래 예제는 filter -> where 을 사용하는 예제이다.
gt는 초과, gte 이상, lt는 미만, lte는 이하
>>> queryset = Event.objects.filter(years_ago__gt=5)
>>> str(queryset.query)
SELECT "events_event"."id", "events_event"."epic_id", "events_event"."details",
"events_event"."years_ago" FROM "events_event"
WHERE "events_event"."years_ago" > 5
2. OR 연산으로 일부 조건을 하나라도 만족하는 항목을 구하려면 어떻게 하나요?
문서에서는 django.contrib.auth의 User를 대상으로 쿼리문을 작성해 테스트하고 있습니다.
연산조건은 아래와 같습니다.
"OR 연산으로 여러 조건 중 하나라도 만족하는 행을 구해야 하는 경우가 많습니다. 이름이 ‘R’로 시작하거나 성이 ‘D’로 시작하는 모든 사용자를 구한다고 해 봅시다."
username이 'R'로 시작하거나 first_name이 'D' 로 시작하는 모든 사용자를 구해야 합니다.
비교에 쓰일 적당한 데이터를 생성해주겠습니다.
User.objects.create(
username='Gilbong',
first_name='Gil',
last_name='bong',
email='gilbong@gmail.com'
)
User.objects.create(
username='Ricky',
first_name='Ricky',
last_name='Dayal',
email='ricky@gmail.com'
)
# 1
queryset = User.objects.filter(
first_name__startswith='R') | User.objects.filter(
last_name__startswith='D')
# 2
from django.db.models import Q
queryset = User.objects.filter(Q(first_name__startswith='R') | Q(last_name__startswith='D'))
# 1과 2의 방법 둘 다 똑같은 쿼리문을 요청합니다.
육안으로 보기에 # 2가 더 깔끔해보이기 때문에 Q를 사용하는 것이 더 좋아보입니다.
queryset을 출력해보면 Ricky의 정보만 출력되는 것을 확인할 수 있습니다.
3. AND 연산으로 여러 조건을 모두 만족하는 항목을 구하려면 어떻게 하나요?
"AND 연산으로 여러 조건을 모두 만족하는 행을 구해야 하는 경우가 많습니다. last_name이 ‘D’로 시작하고 first_name이 ‘R’로 시작하는 모든 사용자를 구한다고 해 봅시다."
# 1
# filter는 조건에 맞다면 출력을 하는 조건문이기 때문에 AND 연산의 &를 굳이 붙일 필요가 없습니다.
queryset = User.objects.filter(first_name__startswith='R', last_name__startswith='D')
# 2
# Q를 사용하는 방안
queryset = User.objects.filter(Q(first_name__startswith='R') & Q(last_name__startswith='D'))
4. NOT 연산으로 조건을 부정하려면 어떻게 하나요?
"id < 5 라는 조건을 만족하지 않는 모든 사용자를 구해 봅시다. 이를 수행하려면 NOT 연산이 필요합니다."
exclude(<condition>)와 filter(~Q(<condition>)) 으로 구할 수 있습니다.
~Q 방법은 처음보는 방법이네요 아직 갈 길이 먼거 같습니다. ㅎㅅㅎ
# 1
# include 포함하다, exclude 포함하지 않다.
# id가 5미만이지 않은 것을 가져와라
>>> queryset = User.objects.exclude(id__lt=5)
>>> queryset
# 2
# 위와 결과는 같다.
>>> queryset = User.objects.filter(~Q(id__lt=5))
5. 동일한 모델 또는 서로 다른 모델에서 구한 쿼리셋들을 합할 수 있나요?
SQL 에서는 여러 개의 결과 집합을 합할 때 UNION 연산을 이용합니다.
Django ORM에서 union 메서드를 이용해 쿼리셋을 합할 수 있습니다.
합하려는 쿼리셋의 모델이 서로 다른 경우, 각 쿼리셋에 포함도니 필드와 데이터 유형이 서로 맞아야 합니다.
UNION Join은 중복을 제거하여 조인한다.
# 1 UNION JOIN SUCCESS
q1 = User.objects.filter(id__lt=5)
# <QuerySet [<User: Gilbong>]>
q2 = User.objects.exclude(id__lt=5)
# <QuerySet [<User: Ricky>]>
q1.union(q2)
<QuerySet [<User: Gilbong>, <User: Ricky>]>
q2.union(q1)
<QuerySet [<User: Gilbong>, <User: Ricky>]>
# 2 UNION JOIN FAIL
q3 = EventVillain.objects.all()
q1.union(q3)
union 메서드는 합하려는 쿼리셋의 필드와 데이터 유형이 서로 일치할 때만 실행할 수 있습니다.
그래서 # 2의 명령이 실패하였습니다.
결국 필드가 같다면 UNION JOIN이 실행된다는 것이고,
Hero 모델과 Villanin 모델은 둘 다 name, gender 필드를 갖고 있습니다.
values_list로 해당 데이터만 가져온 뒤 UNION JOIN을 수행할 수 있습니다.
# 1
Hero.objects.all().values_list(
"name", "gender"
).union(
Villain.objects.all().values_list(
"name", "gender"
))
# 1을 수행한다면 중복 된 데이터는 제거되고 name과 gender의 정보가 출력 될 것 입니다.
6. 필요한 열만 골라 조회하려면 어떻게 하나요?
쿼리셋의 values 메서드와 values_list 메서드, only 메서드
"이름이 R로 시작하는 모든 사용자의 이름(first_name)과 성(last_name)을 구해 봅시다. 데이터베이스 시스템의 부하를 줄이기 위해 그 외의 열은 가져오지 않겠습니다."
# 1 values 사용
queryset = User.objects.filter(first_name__startswith='R').values('first_name', 'last_name')
queryset
<QuerySet [{'first_name': 'Ricky', 'last_name': 'Dayal'}]> # 사전의 리스트 형식
# 2 only 사용
# values 메서드와 다른 점은 id 필드를 함께 가져온다는 점이다.
queryset = User.objects.filter(first_name__startswith='R').only('first_name', 'last_name')
queryset
<QuerySet [<User: Ricky>]>
# 1 value 의 경우 사전의 리스트 형식으로 데이터가 출력되므로 안의 데이터를 사용하려면
queryset[0] 으로 list를 벗기고, dict 호출하는 방법으로 호출을 해주면 됩니다.
ex) queryset[0].get('first_name') -> 'Ricky'
# 2 only 의 경우 values 와 다른 점은 id 필드를 함께 가져온다는 것 입니다.
실제 데이터가 나오는 형식은 인스턴스 변수를 호출하는 것 처럼 사용됩니다.
ex) queryset[0].first_name
ex) queryset.values('') 쿼리셋에서 사용할 수 있는 것들을 다 사용할 수 있습니다.
7. Django 에서 서브쿼리 식을 사용할 수 있나요?
서브쿼리를 사용할 수 있습니다.
ex) from django.db.models import Subquery
# 1 Subquery 사용
from django.db.models import Subquery
users = User.objects.all()
UserParent.objects.filter(user_id__in=Subquery(users.valeus('id'))) # 1-1
UserParent.objects.filter(user_id__in=users.values('id')) # 1-2
그렇다면 # 1-1 과 # 1-2 의 차이는 무엇일까..출력해보니 결과는 똑같이 출력된다.
2020-04-15
'Web > Django' 카테고리의 다른 글
Django - Multiple databases(using, inspectdb) (0) | 2020.04.20 |
---|---|
Django - 1월~12월까지의 데이터 가져오면서 변수명 만들기 (0) | 2020.04.20 |
Django REST Framework JWT를 알아보자 (0) | 2020.03.21 |
Django - messages 활용 (2) | 2020.02.01 |
Django - pipeline (0) | 2020.01.31 |
댓글