[django][ORM]QuerySet API 자주쓰는 메서드

Hyeseong·2020년 12월 26일
0

django

목록 보기
26/35

곡 정보
  • 노래: No Clue (feat. Kara Chenoa)
  • 아티스트 : Jevin Julian
  • 앨범: Jevin Julian
Example This is a dropdown with text!

Django의 QuerySet method를 간단하게 짚고 넘어가 볼게요.
모든 QuerySet method에 대해 낱낱이 분석하고 실습하는것에는 상당한 시간과 노력이 필요하니 일단 기본적인 것들의 토대에서 살을 필요할 때 마다 붙여 나가는 방식으로 진행하는 것이 더 장기적인 관점에서 좋을 것 같아요.

🚑Model Method

- all()

- filter()

- exclude()

- values()

- values_list()

- get()

- create()

- count()

- exists()

- update()

- delete()

- first()

- last()

🚗QuerySet을 return(O) 할 때

<QuerySet [<Category: Category object (1)>, <Category: Category object (2)>]>

자주 쓰는 5개의 QuerySet 메소드를 다뤄 볼게요.

all()

테이블의 모든 컬럼의 레코드를 가져오고 싶다?!
그럴때 사용합니다.

중요한건 리스트로 반환되지만 내부에는 객체가 있다는거!

다른건 다 잊어도 이것만 기억하세요!

다 줘~!

In  : Category.objects.all()
Out : <QuerySet [<Category: Category object (2)>, <Category: Category object (3)>, <Category: Category object (4)>, <Category: Category object (5)>, <Category: Category object (6)>, <Category: Category object (7)>]>

In  : for category in Category.objects.all()
					print(category.name)

#아래와 같이 인스턴스들이 담겨 있는 QuerySet이 반환되기 때문에, 모든 속성에 접근해서 데이터를 관리할 수 있습니다.
Out : 티
      브루드커리
      브루드커피
      콜드브루
      콜드브루

filter()

한 테이블의 특정 레코드를 가져오려면 필터를 사용할 수 있다. filter() method는 가장 자주 사용하는 필터 기능이다. filter(**kwargs): 키워드 인자로 주어진 lookup 조건에 일치하는 레코드들의 QuerySet을 반환한다.

**case1**
In  : Category.objects.filter(name='브루드커피')
Out : [<Category: Category object (3)>, <Category: Category object (4)>]

**case2**
In  : Category.objects.filter(name='브루드커피').filter(id=3)
Out : [<Category: Category object (3)>]

**case3**
In  : Category.objects.filter(name='브루드커피').exclude(id=3)
Out : [<Category: Category object (4)>]

exclude()

lookup parameters와 매칭되지 않는 객체를 반환해요.

exclude() 메서드에 인자값 두개를 넣으면 AND 연산으로 처리되어 2가지 조건에 해당하지 않는 QuerySet을 반환해요.

아래 조건은 2005년1월3일 이후의 헤드라인이 Hello이 객체를 빼고 전부 가져오겠다는거에요

Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')

참고로 exclude(인자1개).exclude(인자1개) 요렇게 사용해도 되요.

Q() 객체를 이용하면 저렇게 길~~게 쓸 필요가 없이 OR, AND, NOT 연산을 더 편리하게 할 수 있어요.

values()

iterable로 사용될 때 모델 인스턴스가 아닌 dictionary을 포함하는 QuerySet을 반환합니다.

In  : Category.objects.filter(name='브루드커피')
Out : [<Category: Category object (3)>, <Category: Category object (4)>]

In  : Category.objects.filter(name='브루드커피').values()
Out : <QuerySet [{'id': 3, 'name': '브루드커피', 'created_at': datetime.datetime(2020, 9, 8, 5, 43, 30, 4068, tzinfo=<UTC>), 'updated_at': datetime.datetime(2020, 9, 8, 5, 43, 30, 21801, tzinfo=<UTC>)}, {'id': 4, 'name': '브루드커피', 'created_at': datetime.datetime(2020, 9, 8, 5, 43, 30, 4068, tzinfo=<UTC>), 'updated_at': datetime.datetime(2020, 9, 8, 5, 43, 30, 21801, tzinfo=<UTC>)}]>

values_list()

values_list() method는 dictionary를 반환하는 대신 반복 될 때 튜플을 반환한다는 점을 제외하면 values ()와 유사합니다. 각 튜플에는 values_list () 호출에 전달 된 각 필드 또는 표현식의 값이 포함되어 있으므로 첫 번째 항목이 첫 번째 필드입니다.

In  : Category.objects.filter(name='브루드커피').values_list()
Out : <QuerySet [(3, '브루드커피', datetime.datetime(2020, 9, 8, 5, 43, 30, 4068, tzinfo=<UTC>), datetime.datetime(2020, 9, 8, 5, 43, 30, 21801, tzinfo=<UTC>)), (4, '브루드커피', datetime.datetime(2020, 9, 8, 5, 43, 30, 4068, tzinfo=<UTC>), datetime.datetime(2020, 9, 8, 5, 43, 30, 21801, tzinfo=<UTC>))]>

🛹QuerySet을 return(X) 하지 않을 때

<Category: Category object (1)> , 9 , True ..

구체적으로 한번 파 볼까요?

우선 QuerySet을 반환한다는건 뭘까요?
쿼리셋(QuerySet)은 전달받은 모델의 객체 목록을 말합니다. 데이터베이스로부터 데이터를 읽고 필터를 걸거나 정렬 등을 할 수 있어요. 리스트와 구조는 같지만 파이썬 기본 자료구조가 아니기에 읽고 쓰기 위해서는 자료형 변환을 해줘야합니다. 쿼리셋은 데이터베이스의 여러 레코드(row)를 나타냅니다.

1. create() Table에 데이터를 추가`INSERT` 해주는 메서드로~ 생성된 `인스턴스`!!!를 반환해줘요. queryset이 아니라.

In  : Category.objects.create(name='콜드브루')
Out : <Category: Category object (1)>
  
# 인스턴스로 반환되니 .(도트연산자)를 이용하여 속성 값에 접근 할 수 있어요.
In  : category = Category.objects.create(name='코드브루')
In  : category.name

cf.) save() : INSERT OR UPDATE
Category(name='콜드브루').save()
2. get() 딱! 한 녀석만 잡는다. - 없는 경우 오류 발생하고 - 값이 2개 이상 확인되어도 오류가 발생합니다.

In  : Category.objects.create(name='콜드브루')
Out : <Category: Category object (1)>
  
# 인스턴스로 반환되니 .(도트연산자)를 이용하여 속성 값에 접근 할 수 있어요.
In  : category = Category.objects.create(name='코드브루')
In  : category.name

cf.) save() : INSERT OR UPDATE
Category(name='콜드브루').save()
3. update() 지정된 필드에 대해 업데이트 쿼리를 수행하고 일치하는 행 수를 반환한다. (일부 행에 이미 새 값이있는 경우 업데이트 된 행 수와 같지 않을 수 있음). 기존에 있는 걸 바꿔치기 한다고 생각하면 편합니다. 종종 1개만 바꿔야지 생각했는데, 다른 동일한 값들도 통째로 바꿔지니 이점 유의해서 사용하세요
In  : Category.objects.filter(name='탄산').update(name='콜드브루')
Out : 2 #총 업데이트된 row 개수
4. delete() QuerySet의 모든 행에 대해 SQL 삭제 쿼리를 수행하고 삭제 된 개체 수와 개체 유형별 삭제 횟수가 있는 dictionary를 반환합니다. ```python In : Category.objects.filter(name='qp').delete() Out :(1, {'products.Category': 1}) ``` 5. save() INSERT or UPDATE를 수행하는 메소드로 단일 객체에 대해서 업데이트를 수행할 때 많이 사용됨

In  : category.objects.GET(id=2)
Out : <Category: Category object (2)>

In  : category.name
Out : '브루드 커피'

In  : category.name = 'new name'
Out : category.save()

In  : category.name
Out :'new name'

  
6. exists() `filter()`와 같이 사용해서 filter조건에 맞는 data가 있으면 `True` 없으면 `False` return~!

In  : Category.objects.filter(name='브루드커피').exists()
Out : True
  

아래 7 ~ 12번 까지는 추가적으로 확인하는 바이니 짚고 넘어가시고 이전에 나온 1~6번 메소드는 우선순위가 더 높으니 그 부분 고려해서 스터디 하길.

7. get_or_create()

객체(object)를 조회할 때 유용하게 사용되는 메서드이다. 이 메서드는 (object, created) 라는 튜플 형식으로 반환을 한다. 첫번째 인자(object)는 우리가 꺼내려고 하는 모델의 인스턴스이고, 두번째 인자(created)는 boolean flag이다.

flag란, TRUE 또는 FALSE를 갖는 온오프 스위치라고 생각하면 된다.

TRUE 라면 인스턴스가 get_or_create 메서드의 문자 그대로인 create에 해당되어 생성되었다는 걸 의미한다.

FALSE 반대로 'get'에 기존 객체가 존재하기에 get() 방식으로 하나의 data를 가져오게된다.

follow, is_follow = self.follow_relations.get_or_create(to_user=user)
    if not is_follow: 
        follow.delete()
    else:
        return follow

is_follow는 TRUE 또는 FALSE를 갖고 있는 flag이다.
is_follow가 TRUE라면 팔로우되지 않은 상태이고 user를 팔로잉한다는 것을 의미한다.

반대로, is_follow가 FALSE라면 기존 DB에 이미 인스턴스가 이미 있다는 상태(즉, 이미 팔로잉 관계)를 의미한다.

is_follow가 TRUE 또는 FALSE라는 값만을 가질 수 있다는 것을 생각하면 if not is_follow의 의미는 값이 FALSE라는 의미이며, if is_follow!와 같은 의미이다.

즉, 값이FALSE일때는 follow.delete()를 해줌으로써 언팔로우를 한다. 그 반대의 경우(즉, 처음으로 get_or_create 메서드에 의해 새로운 인스턴스가 만들어진 경우)라면 팔로우를 한다는 의미이다.

8. bulk_create()

파이썬은 매우 직관적이고 단순한 것이 코드 철학인만큼 메서드 이름 역시 동일한 철학으로 그 결과가 나타나게되요.

네~~! bulk 대량을 의미하고 create는 만들다는 의미이니!

대량으로 만들겠다. 여러개를 한번의 메서드 동작으로 만들겠다는 의미로 파악되네요.

한마디로 객체 리스트를 DB에 저장하게되요

 >>> Entry.objects.bulk_create([
  	Entry(headline='This is a test'),
  	Entry(headline='This is only a test'),
  	..
  	...
  	....
  ])

> 주의사항

  • save()가 호출되지 않아 pre_save(), post_save() 시그널이 발생하지 않아요.
  • 다중 테이블 상속 시나리오에서는 하위 모델과 작동하지 않는다.
  • 모델의 기본 키가 AutoField 인 경우 기본 키 속성은 특정 데이터베이스 (현재 PostgreSQLMariaDB 10.5 이상)에서만 검색 할 수 있습니다. 다른 데이터베이스에서는 설정되지 않습니다.
  • MTM관계에서 동작하지 않아요
  • 객체를 리스트로 변환한 생성자(generator)인 경우 evaluate하여 처리합니다.
    리스트를 객체로 변환한 작업을 통해서 manual하게 설정한 PK값이 있는 객체를 먼저 삽입하게 되요.
    제너레이터로 evaulate하지 않고 DB에 한큐에 때력박고 싶은 경우 manual로 설정한 pk가 없는한 bulk_create()를 사용할 수 있어요.

evaluation
쿼리셋을 순회하는 시점에, 쿼리셋에 해당하는 DB의 레코드들을 실제로 가져오며(fetch), 이는 모두 Django 모델로 변환된다. 이를 가리켜 evaluation이라고 한다.

from itertools import islice

batch_size = 100
objs = (Entry(headline='Test %s' % i) for i in range(1000))
while True:
    batch = list(islice(objs, batch_size))
    if not batch:
        break
    Entry.objects.bulk_create(batch, batch_size)  

batch_size 매개 변수는 단일 쿼리에서 생성되는 개체 수를 제어합니다. 기본값은 쿼리 당 최대 999 개의 변수가 사용되는 SQLite를 제외하고 모든 개체를 하나의 일괄 처리로 만드는 것입니다.

Oracle은 제외하고 ignore_conflicts 매개 변수를 True로 설정하면 중복 고유 값과 같은 제약 조건에 실패한 행을 삽입하는 데 실패를 무시하도록 데이터베이스에 지시합니다. 이 매개 변수를 활성화하면 각 모델 인스턴스에서 기본 키 설정이 비활성화됩니다 (DB가 지원할때 사용 가능).

경고!

MySQL 및 MariaDB에서 ignore_conflicts 매개 변수를 True로 설정하면 중복 키를 제외한 특정 유형의 오류가 경고로 바뀝니다. 엄격 모드에서도.
예 : invalid values or non-nullable violations.
자세한 내용은 MySQL 문서 및 MariaDB 문서를 참조하세요.

MariaDB 10.5이상 부터는 PK 속성에 대한 fetching이 지원되요.

9. count()

QuerySet과 매칭되는 DB에 있는 객체 수를 Integer 값으로 반환해요.

# 데이터베이스의 총 항목 수를 반환합니다.
Entry.objects.count()

# 제목에 'Lennon'이 포함 된 항목 수를 반환합니다.
Entry.objects.filter(headline__contains='Lennon').count()
  

count()는 DB에서 SELECT COUNT(*)** 진행해요. 그래서 항상 count()를 수행하는게 len()메소드를 호출하여 모든 레코드를 메모리에 올려서 파이썬 객체로 만드는 것보다 더 효율적이에요.(의도적으로 메모리에 올려 작업하고자 하는 경우를 빼고는 말이조)

10. first()

QuerySet과 일치하는 첫 객체를 반환한다. 정렬을 정의하지 않으면 pk으로 자동 정렬한다. 이건 order_by()와 상호작용에 양향을 미칠 수 있다.

p = Article.objects.order_by('title', 'pub_date').first()

# 위 코드는 아래 코드와 동일하게 작동한다.(first()의 간편함을 이용하자.)
try:
    p = Article.objects.order_by('title', 'pub_date')[0]
except IndexError:
    p = None

count()는 DB에서 SELECT COUNT(*)** 진행해요. 그래서 항상 count()를 수행하는게 len()메소드를 호출하여 모든 레코드를 메모리에 올려서 파이썬 객체로 만드는 것보다 더 효율적이에요.(의도적으로 메모리에 올려 작업하고자 하는 경우를 빼고는 말이조)

11. last()

QuerySet과 일치하는 마지막 객체를 리턴한다. 그외에는 first()와 동일하다.

12. aggregate()

QuerySet에 대해 계산된 집계를 dict으로 리턴한다. 집계로 쿼리식이기 때문에 다른 집계나 값과 결합하여 복잡한 집계를 만들 수 있다.

키워드 인수를 사용하여 집계된 집계는 키워드를 주석의 이름으로 사용한다. 익명 인수는 집계함수의 이름과 집계되는 모델 필드에 따라 이름이 생성된다. 복합 집계는 익명 인수를 사용할 수 없기 때문에 별칭을 지정해주어야 한다.

>>> from django.db.models import Count
>>> q = Blog.objects.aggregate(Count('entry'))
{'entry__count': 16}

keyword argument 사용

>>> q = Blog.objects.aggregate(number_of_entries=Count('entry'))
{'number_of_entries': 16}  
#from django.db.models import Count, Avg, Min, Max, Sum으로 집계 함수를 import한다.


>>> from django.db.models import Count, Avg, Min, Max, Sum 

>>> Review.objects.all().aggregate(Avg('score'))
{'score__avg': 4.725}

>>> Review.objects.all().aggregate(Min('score'))
{'score__min': 3.9}

>>> Review.objects.all().aggregate(Max('score'))
{'score__max': 5.0}

>>> Review.objects.all().aggregate(Sum('score'))
{'score__sum': 37.8}
  

aggregate() 메서드 및 ORM으로 groupby 연습하기

profile
어제보다 오늘 그리고 오늘 보다 내일...

0개의 댓글