[Django] Django ORM Cookbook : 2

GreenBean·2021년 9월 30일
0

Django ORM Cookbook

목록 보기
2/8
post-thumbnail

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]))


# 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"
  • 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>]>


# annotate 메서드로 계산해 둔 필드를 가리킬 때
>>> User.objects.annotate(first=Substr("first_name", 1, 1), last=Substr("last_name", 1, 1))
		.filter(first=F("last"))

<QuerySet [<User: Guido>, <User: Tim>]>

파일이 없는 행

  • 장고의 FileFieldImageField는 파일과 이미지 파일의 경로를 저장
    • 이것은 응용 수준에서의 구별이고, 데이터베이스 수준에서는 모두 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 구문을 이용해 필요한 데이터만 읽어옴
# 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]
profile
🌱 Backend-Dev | hwaya2828@gmail.com

0개의 댓글