[Django] Django ORM Cookbook : 3

GreenBean·2021년 9월 30일
0

Django ORM Cookbook

목록 보기
3/8
post-thumbnail

Django ORM

Django ORM Cookbook


고유한 필드 값을 가진 항목

  • 이름이 다른 사용자와 겹치지 않은 사용자를 찾는 경우
>>> distinct = User.objects.values('first_name')
		           .annotate(name_count=Count('first_name'))
                           .filter(name_count=1)


>>> records = User.objects.filter(first_name__in=[item['first_name'] for item in distinct])
  • 한편, User.objects.distinct("first_name").all()과 같은 코드는 고유한 first_name을 가진 사용자별로 첫번째 사용자를 구하는 코드
    • 위 코드와는 실행 결과가 다름

Q 객체 이용

  • Q 객체를 이용하면 SQL 질의문의 WHERE 절에 해당하는 기능을 온전히 활용할 수 있음
# OR 연산 수행
>>> from django.db.models import Q
>>> queryset = User.objects.filter(Q(first_name__startswith='R') | Q(last_name__startswith='D'))
>>> queryset

<QuerySet [<User: Ricky>, <User: Ritesh>, <User: Radha>, <User: Raghu>, <User: rishab>]>


# AND 연산 수행
>>> queryset = User.objects.filter(Q(first_name__startswith='R') & Q(last_name__startswith='D'))
>>> queryset

<QuerySet [<User: Ricky>, <User: Ritesh>, <User: rishab>]>


# AND & NOT 연산 수행
>>> queryset = User.objects.filter(Q(first_name__startswith='R') & ~Q(last_name__startswith='Z'))

# 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%
       AND NOT ("auth_user"."last_name"::text LIKE Z%))

항목의 집계

  • 장고 ORM을 이용해 항목을 생성·조회·갱신·삭제할 수 있지만, 때로는 항목들의 집계값을 구하고 싶을 때가 있음
    • 장고 ORM에는 SQL의 일반적인 집계 기능을 수행하는 Max, Min, Avg, Sum 등의 함수가 있음
>>> from django.db.models import Avg
>>> User.objects.all().aggregate(Avg('id'))

{'id__avg': 7.571428571428571}


from django.db.models import Max
>>> User.objects.all().aggregate(Max('id'))

{'id__max': 15}


from django.db.models import Min
>>> User.objects.all().aggregate(Min('id'))

{'id__min': 1}


from django.db.models import Sum
>>> User.objects.all().aggregate(Sum('id'))

{'id__sum': 106}

무작위 항목

  • 항목 가운데 하나를 무작위로 구해야 할 때, 두 가지 방법
    • order_by 메서드로 항목들을 정렬할 때, 정렬 기준을 ‘무작위’로 지정하는 것
      • 데이터를 무작위로 정렬하여 첫 번째 항목을 가져오면 무작위 항목을 구할 수 있음
    • 전체 표를 정렬하는 대신 저장된 항목의 마지막 ID를 이용하는 것
      • 표에서 ID의 최대값을 구하고, 1과 마지막 ID 사이의 난수를 하나 생성
      • ID가 이 난수와 동일한 항목을 구하면 됨
# order_by 메서드 이용
# 주의: 사용하는 데이터베이스 시스템에 따라 order_by('?') 의 실행 비용이 비싸고 성능이 느릴 수 있음
def get_random():
    return Category.objects.order_by("?").first()


# 저장된 항목의 마지막 ID를 이용
>>> from django.db.models import Max
>>> import random
>>> def get_random2():
     max_id = Category.objects.all().aggregate(max_id=Max("id"))['max_id']
     pk = random.randint(1, max_id)
     return Category.objects.get(pk=pk)


>>> get_random2()

<Category: e2c3a10d3e9c46788833c4ece2a418e2>

>>> get_random2()

<Category: f164ad0c5bc8300b469d1c428a514cc1>


# get_random2()는 항목을 삭제하거나 해서 ID가 중간에 비어있는 경우에는 쓸 수 없음
# 그런 경우에는 유효한 값이 나올 때까지 반복하도록 하면 됨, 그 방식으로 위의 함수를 수정
>>> def get_random3():
     max_id = Category.objects.all().aggregate(max_id=Max("id"))['max_id']
     
     while True:
         pk = random.randint(1, max_id)
         category = Category.objects.filter(pk=pk).first()
         if category:
             return category


>>> get_random3()

<Category: 334aa9926bd65dc0f9dd4fc86ce42e75>

>>> get_random3()

<Category: 4092762909c2c034e90c3d2eb5a73447>

지원하지 않는 데이터베이스 함수 사용

  • 장고에는 Lower, Coalesce, Max 등의 데이터베이스 함수가 포함
  • 하지만 장고가 데이터베이스가 지원하는 모든 함수를 제공하는 것은 아님
    • 특히, 특정 데이터베이스 시스템의 전용 함수들은 제공되지 않음
  • 장고가 제공하지 않는 데이터베이스 함수를 실행하기 위해서는 장고의 Func 객체를 사용하면 됨
  • PostgreSQL에는 fuzzystrmatch 확장 기능이 있음
    • 이 확장에는 텍스트 데이터의 유사도를 측정하기 위한 함수가 여러 가지 포함되어 있음
    • PostgreSQL 데이터베이스 셸에서 create extension fuzzystrmatch를 실행하여 이 확장을 설치
# name 이 ‘Zeus’ 와 비슷한 Hero 항목들
>>> from django.db.models import Func, F
>>> Hero.objects.annotate(like_zeus=Func(F('name'), function='levenshtein', template="%(function)s(%(expressions)s, 'Zeus')"))
  • like_zeus=Func(F('name'), function='levenshtein', template="%(function)s(%(expressions)s, 'Zeus')") 코드에서 Func 객체를 세 개의 인자로 초기화
    • 첫 번째 인자는 함수에 적용할 열
    • 두 번째 인자는 데이터베이스에서 실행할 함수의 이름
    • 세 번째 인자는 함수를 실행할 SQL 질의문의 템플릿
# 클래스 확장
class LevenshteinLikeZeus(Func):
    function='levenshtein'
    template="%(function)s(%(expressions)s, 'Zeus')"


>>> Hero.objects.annotate(like_zeus=LevenshteinLikeZeus(F("name"))
		.filter(like_zeus__lt=2)

<QuerySet [<Hero: Zeus>, <Hero: ZeuX>, <Hero: Xeus>]>
profile
🌱 Backend-Dev | hwaya2828@gmail.com

0개의 댓글