일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 알고리즘 문제
- 알고리즘
- 백준
- Git
- 파이썬 알고리즘
- react
- 알고리즘 풀이
- java
- Baekjoon
- Django
- 알고리즘 연습
- es6
- 장고
- 파이썬
- js
- DRF
- API
- form
- CSS
- c++
- web
- PYTHON
- AWS
- django ORM
- javascript
- MAC
- django rest framework
- HTML
- django widget
- Algorithm
- Today
- Total
수학과의 좌충우돌 프로그래밍
[Django] ORM Cookbook, 정보를 조회하고 필요한 항목을 선별하는 방법(1) 본문
해당 포스팅은 Django ORM CookBook 을 공부하며 새로 알게된 사실이나 더 나아가 추가적으로 같이 알면 좋을 내용을 정리하고 있습니다.
1. 장고 ORM이 실행하는 실제 SQL 질의문 확인
ORM에 대응되는 SQL 질의문을 확인하기 위해서는 다음과 같이 확인할 수 있습니다.
queryset = Post.objects.all()
str(queryset.query)
'''
'SELECT `post_post`.`id`, `post_post`.`user_id`, `post_post`.`title`, `post_post`.`content`, `post_post`.`kind`, `post_post`.`image`, `post_post`.`created_at`, `post_post`.`updated_at`
FROM `post_post`'
'''
필요한 순간에 쿼리를 확인해볼 수 있지만 개인적으로 logging을 통해서 모든 쿼리를 직접 확인하여 있어 위와 같은 방법은 많이 사용하지 않습니다.
2. OR 연산
쿼리를 날리다보면 OR 연산이 필요한 경우가 많이 있습니다.
ORM에서는 이를 두 가지 방법으로 구현이 가능합니다.
이름이 첫번째가 R로 시작하거나 D로 끝나는 유저들을 조회해봅시다.
-
queryset1 | queryset2
queryset = User.objects.filter( first_name__startswith='R' ) | User.objects.filter( last_name__startswith='D' )
-
Q 객체 이용하기
from django.db.models import Q queryset = User.objects.filter(Q(first_name__startswith='R')|Q(last_name__startswith='D'))
이와 매칭되는 SQL 질의문은 다음과 같습니다.
'SELECT "auth_user"."id", "auth_user"."password", "auth_user"."last_login", "auth_user"."is_superuser", "auth_user"."username", "auth_user"."first_name", "auth_user"."last_name", "auth_user"."email", "auth_user"."is_staff", "auth_user"."is_active", "auth_user"."date_joined"
FROM "auth_user"
WHERE ("auth_user"."first_name"::text LIKE R% OR "auth_user"."last_name"::text LIKE D%)'
3. AND 연산
OR 연산과 마찬가지로 AND 연산도 많이 사용됩니다.
하지만 AND 연산은 filter에 조건을 여러 개 넘겨줌으로서 쉽게 구현할 수 있습니다.
- filter(condition1, condition2)
- queryset1 & queryset2
- Q 객체 이용하기
4. NOT 연산
NOT 연산도 아래와 같이 두 가지 방법으로 구현이 가능합니다.
id < 5 라는 조건을 만족하지 않는 모든 사용자를 구해봅시다.
-
exclude(conditions)
queryset = User.objects.exclude(id__lt=5)
-
filter(~Q(conditions))
from django.db.models import Q queryset = User.objects.filter(~Q(id__lt=5))
5. UNION 연산
SQL에서는 여러 결과를 UNION으로 묶어 줄 수 있습니다.
장고 ORM에서도 union 메서드를 이용해 쿼리셋을 합칠 수 있습니다.
단 합치기 위해서는 각 쿼리셋의 필드의 수와 데이터 유형이 일치해야합니다.
q1 = User.objects.filter(id__gte=5)
q2 = User.objects.filter(id__lte=9)
q1.union(q2)
다른 모델이더라도 동일한 필드만 가져와 union을 해줄 수 있습니다.
post = Post.objects.all().values_list('title', 'content')
comment = Comment.objects.all().values_list('title', 'content')
post.union(comment)
SELECT `post_post`.`title`, `post_post`.`content`
FROM `post_post`
UNION
SELECT `post_comment`.`title` ,`post_comment`.`content`
FROM `post_comment`
6. SELECT
ORM을 통해 일반적으로 쿼리를 날리면 모든 필드에 대해서 데이터를 가져옵니다.
하지만 필드가 전부 필요하지 않은 경우에 이는 불필요한 연산입니다.
ORM에서도 특정 필드만을 읽어오는 기능을 제공해줍니다.
-
values
post = Post.objects.all().values('title', 'content') # <QuerySet [{'title': '첫번째 Post', 'content': '첫번째 내용입니다.'}, {'title': '두번째 Post', 'content': '두번째 내용입니다.'}]>
반환되는 결과는 딕셔너리들의 배열입니다.
-
values_list
post = Post.objects.all().values_list('title', 'content') # <QuerySet [('첫번째 Post', '첫번째 내용입니다.'), ('두번째 Post', '두번째 내용입니다.')]>
반환되는 결과는 튜플의 배열입니다.
-
only
post = Post.objects.all().only('title', 'content') # <QuerySet [<Post: arkss의 첫번째 Post>, <Post: arkss의 두번째 Post>]>
반환되는 결과는 모델 객체의 배열입니다.
위와 다른 점은 SQL에서 id도 함께 가져옵니다.
SELECT `post_post`.`id`, `post_post`.`title`, `post_post`.`content` FROM `post_post`
7. 서브쿼리
ORM에서 서브쿼리를 사용 할 수 있습니다.
서브쿼리를 위해 아래와 같은 모델을 정의하였습니다.
class Category(models.Model):
name = models.CharField(max_length=100)
class Hero(models.Model):
# ...
name = models.CharField(max_length=100)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
benevolence_factor = models.PositiveSmallIntegerField(
help_text="How benevolent this hero is?",
default=50
)
Hero 중에 benevolence_factor가 가장 높은 Hero를 구해봅시다.
우선 서브쿼리를 먼저 정의합니다.
benevolence_factor
에 대해 내림차순으로 정렬하여 높은 값이 가장 상위에 위치하도록 하고 OuterRef
를 통해 외부 테이블의 pk와 비교하여 같을 때만 반환할 수 있도록 합니다.
hero_qs = Hero.objects.filter(
category=OuterRef("pk")
).order_by("-benevolence_factor")
이제 본 쿼리를 정의합니다.
영웅의 name
만 필요하므로 values로 가져오고 [:1] 를 통해서 제일 상위의 영웅만 조인합니다.
Category.objects.all().annotate(
most_benevolent_hero=Subquery(
hero_qs.values('name')[:1]
)
)
최종적으로 다음과 같습니다.
hero_qs = Hero.objects.filter(
category=OuterRef("pk")
).order_by("-benevolence_factor")
Category.objects.all().annotate(
most_benevolent_hero=Subquery(
hero_qs.values('name')[:1]
)
)
이에 대한 SQL 질의문은 다음과 같습니다.
SELECT "entities_category"."id",
"entities_category"."name",
(SELECT U0."name"
FROM "entities_hero" U0
WHERE U0."category_id" = ("entities_category"."id")
ORDER BY U0."benevolence_factor" DESC
LIMIT 1) AS "most_benevolent_hero"
FROM "entities_category"
8. 필드값 비교하여 SELECT
상황에 따라서 필드 값을 비교하여 쿼리를 날릴 수 있습니다.
(맨날 날린다고만 표현해서 블로그에 쓰기에 적합한 용어는 아니라고 생각하지만 적절한 단어를 못 찾겠어요..)
이 때는 F
객체를 사용합니다.
다음과 같은 상황을 고려해봅시다.
Post 모델 중 생성된 시각과 최근 수정 시각이 같은 데이터를 가져오기 위해서는 다음과 구할 수 있습니다.
from django.db.models import F
post = Post.objects.filter(created_at=F("updated_at"))
또한 F
객체에 __gt
, __lt
등의 룩업을 적용하는 것 또한 가능합니다.
9. FileField에 파일이 들어있지 않은 행
FileField
와 ImageField
모두 해당 파일의 경로를 문자열로 저장합니다.
따라서 파일이 없는 행을 아래와 같이 구할 수 있습니다.
from django.db.models import Q
no_files_objects = MyModel.objects.filter(
Q(file='')|Q(file=None)
)
'웹프로그래밍 > Django' 카테고리의 다른 글
[Django] ORM Cookbook, 정보를 조회하고 필요한 항목을 선별하는 방법(3) (0) | 2020.08.27 |
---|---|
[Django] ORM Cookbook, 정보를 조회하고 필요한 항목을 선별하는 방법(2) (0) | 2020.08.27 |
[Django] 한 모델이 여러 개의 모델과 관계를 맺어야하는 순간, contenttypes framework (2) | 2020.08.21 |
[Django] Logging 설정 및 SQL 쿼리 확인 (0) | 2020.08.15 |
[Django] AWS, S3를 이용한 이미지 업로드 (0) | 2020.08.12 |