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 절에 해당하는 기능을 온전히 활용할 수 있음
>>> 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>]>
>>> queryset = User.objects.filter(Q(first_name__startswith='R') & Q(last_name__startswith='D'))
>>> queryset
<QuerySet [<User: Ricky>, <User: Ritesh>, <User: rishab>]>
>>> queryset = User.objects.filter(Q(first_name__startswith='R') & ~Q(last_name__startswith='Z'))
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가 이 난수와 동일한 항목을 구하면 됨
def get_random():
return Category.objects.order_by("?").first()
>>> 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>
>>> 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
를 실행하여 이 확장을 설치
>>> 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>]>