Django ORM
Django ORM Cookbook
서브쿼리식 사용
- SQL 서브쿼리(subquery, 질의문 내의 하위 질의)식 사용 가능
>>> from django.db.models import Subquery
>>> users = User.objects.all()
>>> UserParent.objects.filter(user_id__in=Subquery(users.values('id')))
<QuerySet [<UserParent: UserParent object (2)>, <UserParent: UserParent object (5)>, <UserParent: UserParent object (8)>]>
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_qs = Hero.objects.filter(category=OuterRef("pk"))
.order_by("-benevolence_factor")
>>> Category.objects.all()
.annotate(most_benevolent_hero=Subquery(hero_qs.values('name')[:1]))
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"
Hero
모델의 항목들을 선함(benevolence_factor
)에 따라 내림차순으로 정렬하여 선택
- 그리고
category=OuterRef("pk")
를 이용해 이 선택이 서브쿼리로 사용될 수 있도록 준비
- 그 뒤
most_benevolent_hero=Subquery(hero_qs.values('name')[:1])
로 서브쿼리에 별칭을 붙여 Category
쿼리셋 안에서 사용
- 이 때,
hero_qs.values('name')[:1]
는 서브쿼리에서 첫 번째 행의 name 필드를 구하는 코드
필드 값 비교하여 항목 선택
- 장고 ORM에서 필드를 고정 값과 비교하여 항목을 선택하는 것은 간단
- 이름(
first_name
) 이 'R' 로 시작하는 User 모델의 행을 구하려면 User.objects.filter(first_name__startswith='R')
와 같이 코드 작성
- 필드와 필드를 서로 비교하는 것도 가능
- 예를 들어, 이름(
first_name
) 을 성(last_name
) 과 비교하여 선택
- 이럴 때
F 객체
를 사용
F 객체
는 annotate 메서드로 계산해 둔 필드를 가리킬 때도 사용 가능
- 예를 들어, 이름의 첫 글자와 성의 첫 글자가 동일한 사용자를 구하고 싶다면
Substr("first_name", 1, 1)
를 사용
F 객체
에 __gt
, __lt
등의 룩업(lookup)을 적용하는 것 또한 가능
>>> User.objects.filter(last_name=F("first_name"))
<QuerySet [<User: Guido>]>
>>> User.objects.annotate(first=Substr("first_name", 1, 1), last=Substr("last_name", 1, 1))
.filter(first=F("last"))
<QuerySet [<User: Guido>, <User: Tim>]>
파일이 없는 행
- 장고의
FileField
와 ImageField
는 파일과 이미지 파일의 경로를 저장
- 이것은 응용 수준에서의 구별이고, 데이터베이스 수준에서는 모두
CharField
와 동일한 방식으로 저장됨
>>> from django.db.models import Q
>>> no_files_objects = MyModel.objects.filter((file='')|Q(file=None))
두 모델 결합
- SQL에서는 JOIN 문을 이용해 동일한 값을 가진 열을 기준으로 두 표를 결합할 수 있음
>>> a1 = Article.objects.select_related('reporter')
>>> a1
<QuerySet [<Article: International News>, <Article: Local News>, <Article: Morning news>, <Article: Prime time>, <Article: Test Article>, <Article: Weather Report>]>
>>> print(a1.query)
SELECT "events_article"."id", "events_article"."headline", "events_article"."pub_date", "events_article"."reporter_id", "events_article"."slug", "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 "events_article" INNER JOIN "auth_user" ON ("events_article"."reporter_id" = "auth_user"."id")
ORDER BY "events_article"."headline" ASC
>>> a2 = Article.objects.filter(reporter__username='John')
>>> a2
<QuerySet [<Article: International News>, <Article: Local News>, <Article: Prime time>, <Article: Test Article>, <Article: Weather Report>]>
>>> print(a2.query)
SELECT "events_article"."id", "events_article"."headline", "events_article"."pub_date", "events_article"."reporter_id", "events_article"."slug"
FROM "events_article" INNER JOIN "auth_user" ON ("events_article"."reporter_id" = "auth_user"."id")
WHERE "auth_user"."username" = John
ORDER BY "events_article"."headline" ASC
인덱싱 연산
- 어떤 필드를 기준으로 데이터를 정렬했을 때, 두 번째 항목을 구해야 하는 경우
- 장고 ORM에서 첫번째 항목은
first()
메서드로, 마지막 항목은 last()
메서드로 구할 수 있음
- N번째 항목을 구하는 메서드는 제공되지 않는 대신, 파이썬의 인덱싱 연산을 이용할 수 있음
>>> user = User.objects.order_by('-last_login')[1]
>>> user.first_name
'Raghu'
>>> user = User.objects.order_by('-last_login')[2]
>>> user.first_name
'Sohan'
User.objects.order_by('-last_login')[2]
와 같이 쿼리셋에 인덱스 연산을 지시할 때
- 장고 ORM은 데이터베이스에서 전체 데이터를 가져온 뒤 인덱싱하는 것이 아니라,
LIMIT ... OFFSET
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"
ORDER BY "auth_user"."last_login" DESC
LIMIT 1
OFFSET 2
특정 열의 값이 동일한 항목
- 특정 열의 값이 동일한 항목을 찾을 때,
Count
를 구한 뒤 중복 수를 기준으로 골라내면 됨
>>> duplicates = User.objects.values('first_name')
.annotate(name_count=Count('first_name'))
.filter(name_count__gt=1)
>>> duplicates
<QuerySet [{'first_name': 'John', 'name_count': 3}]>
>>> records = User.objects.filter(first_name__in=[item['first_name'] for item in duplicates])
>>> print([item.id for item in records])
[2, 11, 13]