수학과의 좌충우돌 프로그래밍

[Django] ORM Cookbook, 정보를 조회하고 필요한 항목을 선별하는 방법(3) 본문

웹프로그래밍/Django

[Django] ORM Cookbook, 정보를 조회하고 필요한 항목을 선별하는 방법(3)

ssung.k 2020. 8. 27. 19:35

해당 포스팅은 Django ORM CookBook 을 공부하며 새로 알게된 사실이나 더 나아가 추가적으로 같이 알면 좋을 내용을 정리하고 있습니다.

 

14. Q 객체

앞서서 Q 객체에 대해 다루었다시피 이를 통해 SQL 질의의 WHERE 절을 수행할 수 있습니다.

queryset = User.objects.filter(
    Q(first_name__startswith='R') & ~Q(last_name__startswith='Z')
)
SELECT "auth_user"."id",
       "auth_user"."password",
       "auth_user"."first_name",
       "auth_user"."last_name"
FROM "auth_user"
WHERE ("auth_user"."first_name"::text LIKE R%
       AND NOT ("auth_user"."last_name"::text LIKE Z%))

 

 

15. 집계함수

Django에서 집계함수를 사용하기 위해서는 aggregate 와 여러 제공되는 함수들을 사용해야합니다.

from django.db.models import Avg, Max, Min, Sum, Count
User.objects.all().aggregate(Avg('id'))
# {'id__avg': 7.571428571428571}
User.objects.all().aggregate(Max('id'))
# {'id__max': 15}
User.objects.all().aggregate(Min('id'))
# {'id__min': 1}
User.objects.all().aggregate(Sum('id'))
# {'id__sum': 106}
User.objects.all().aggregate(Count('id'))
# {'id__count': 14}

 

대표적으로 Count 의 SQL은 아래와 같습니다.

SELECT COUNT(`accounts_user`.`id`) AS `id__count` FROM `accounts_user`

 

 

16. 무작위로 항목 뽑기

무작위로 항목을 뽑는 두 가지 방법에 대해서 알아보겠습니다.

  • order_by

    order_by 메서드로 항목을 정렬 할 때 ? 를 통해서 무작위로 정렬할 수 있습니다. 그 후 첫번째 항목을 가져오면 무작위 항목을 구할 수 있습니다.

    Post.objects.order_by('?').first()
    

    하지만 이 경우 하나의 원소를 가져오기 위해 나머지 원소도 정렬을 하여 실행비용이 비싸고 성능이 느립니다.

 

  • random num

    이번에는 정렬을 하지 않고 id의 마지막 값을 이용하여 인덱싱을 합니다.

    from django.db.models import Max
    import random
    
    max_id = Post.objects.all().aggregate(max_id=Max("id"))['max_id']
    pk = random.randint(1, max_id)
    random_post = Post.objects.get(pk=pk)
    

     

    하지만 이 경우, 중간에 데이터를 삭제하여 id값이 빠져있으면 문제가 발생합니다.

    따라서 유효한 값이 나올 때까지 랜덤한 수를 계속 뽑아줍니다.

    max_id = Post.objects.all().aggregate(max_id=Max("id"))['max_id']
    while True:
        pk = random.randint(1, max_id)
        post =     Post.objects.filter(pk=pk).first()
        if category:
            return category
    

 

두 가지 방법의 성능 차이를 생각해봅시다.

일반적으로 정렬 연산이 적지 않은 비용이 들기 때문에 아래 방법이 더 효율적입니다.

하지만 아래 방법은 중간에 id 값들이 많이 삭제되어 있을 경우 반복이 많아지며 비효율적일 수 있습니다.

상황에 따라 적절한 방법을 선택해야합니다.

 

17. Django가 지원하지 않는 DB 함수

Django에서 범용적인 함수들을 대부분 지원하지만 특정 데이터베이스 시스템의 전용 함수들은 제공되지 않습니다.

이러한 함수를 사용하기 위해서는 Func 객체를 사용합니다.

PostgreSQL에는 fuzzystrmatch 확장 기능이 있습니다. 이 확장에는 텍스트 데이터의 유사도를 측정하기 위한 함수가 여러 가지 포함되어 있습니다.

PostgreSQL 데이터베이스 셸에서 create extension fuzzystrmatch 를 실행하여 이 확장을 설치해줍시다.

 

우선 현재 데이터를 확인해봅시다.

heros = Hero.objects.all()
# <QuerySet [<Hero: Zeus>, <Hero: ZeuX>, <Hero: Xeus>, <Hero: Poseidon>]>

 

위 데이터 중에서 nameZeus 와 비슷한 데이터들을 구해보겠습니다.

from django.db.models import Func, F

# 방법 1

Hero.objects.annotate(like_zeus=Func(F('name'), function='levenshtein', template="%(function)s(%(expressions)s, 'Zeus')"))
# 방법 2

class LevenshteinLikeZeus(Func):
    function='levenshtein'
    template="%(function)s(%(expressions)s, 'Zeus')"
    
    Hero.objects.annotate(like_zeus=LevenshteinLikeZeus(F("name")))

 

Func 객체는 세 개의 인자를 받습니다.

  • 함수에 적용할 칼럼의 이름
  • 실행할 함수의 이름
  • SQL 질의문의 템플릿

 

이렇게 구한 레벤슈타인 거리를 기준으로 이름이 비슷한 항목을 선별할 수 있습니다.

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

# <QuerySet [<Hero: Zeus>, <Hero: ZeuX>, <Hero: Xeus>]>

 

Comments