TIL # 74 : [Django] Query Set Methods (2/2)

셀레스틴 허·2021년 2월 14일
0
post-thumbnail

✅ Django QuerySet Methods

7. QuerySet과 맞는 object 세어주는 메소드(.count())

데이터베이스 안에 있는 객체 중 해당 QuerySet과 맞는 객체 수(정수)를 반환한다.

>>> from posting.models import *
>>> from user.models import *
>>> Posting.objects.count()
8
>>> Posting.objects.filter(username_id=32).count()
1

** 만일 QuerySet에 있는 아이템의 수 및 model instance가 필요하다면 len(QuerySet)을 쓰는게 훨씬 효과적이다. .count()는 추가적인 database query를 만들기 때문이다.

8. QuerySet에 해당 객체가 있는지 확인하는 법(.exists())

해당 메소드는 QuerySet에 결과값이 있으면 True를, 없으면 False를 반환한다.

>>> Posting.objects.filter(username_id=32).exists()
True

9. 특정 field를 업데이트하기(.update())

해당 field에 저장되어있는 데이터를 다른 값으로 업데이트할 수 있다. 해당 Query와 맞는 수(정수)를 반환한다.

  1. 해당 객체를 가져온 다음 field로 옮겨서 값을 업데이트하기. 꼭 save()를 해서 데이터베이스에 해당 변경값을 저장해야 한다.
>>> Posting.objects.get(id=9)
<Posting: Posting object (9)>
>>> q = Posting.objects.get(id=9)
>>> q.description = '바뀔 예정'
>>> q.save()
  1. filter() 적용 후 update()하기. save() 할 필요 없이 바로 데이터베이스에 업데이트 된다.
>>> Posting.objects.filter(id=9).update(description='한번 더 바뀔 예정')
1

10. row 삭제하기 (.delete())

해당 row에 저장되어 있는 데이터를 삭제한다. 해당 Query와 맞는 수(정수),객체 타입과 해당되는 객체 수를 포함한 딕셔너리를 반환한다.

>>> Posting.objects.filter(description='한번 더 바뀔 예정').delete()
(1, {'posting.Posting': 1})

이 때 FK값이 만약 ON DELETE CASCADE으로 저장되어 있으면 삭제할 객체를 가리키는 FK가 있는 객체 또한 함께 삭제된다.

11. 해당 QuerySet과 맞는 첫번째 객체 반환하기(.first())

해당 QuerySet과 맞는 첫번째 객체 반환하며, 만약 맞는 객체가 없을 시 None을 반환한다. 만일 명시된 .order_by()가 없을 경우 자동으로 PK값으로 나열한다.

>>> Posting.objects.order_by('description').first()
<Posting: Posting object (2)>
>>> Posting.objects.order_by('description')[0]
<Posting: Posting object (2)>

order_by('description').first()order_by('description')[0] 둘 다 똑같은 값을 반환한다.

12. 해당 QuerySet과 맞는 마지막 객체 반환하기(.last())

해당 QuerySet과 맞는 마지막 객체 반환하며, 만약 맞는 객체가 없을 시 None을 반환한다. .first() 메소드처럼 만일 명시된 .order_by()가 없을 경우 자동으로 PK값으로 나열한다.

>>> Posting.objects.order_by('description').last()
<Posting: Posting object (7)>
>>> Posting.objects.order_by('description')[-1]
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/mjhuh/miniconda3/envs/westagram/lib/python3.9/site-packages/django/db/models/query.py", line 301, in __getitem__
    assert ((not isinstance(k, slice) and (k >= 0)) or
AssertionError: Negative indexing is not supported.
>>> Posting.objects.order_by('description')[6]
<Posting: Posting object (7)>

Negative indexingAssertionError를 띄운다.

13. 순서로 정렬하기(.order_by())

>>> Posting.objects.filter(username_id=1).order_by('?')
<QuerySet [<Posting: Posting object (2)>, <Posting: Posting object (3)>, <Posting: Posting object (8)>]>
>>> Posting.objects.filter(username_id=1).order_by('-image_url')
<QuerySet [<Posting: Posting object (3)>, <Posting: Posting object (2)>, <Posting: Posting object (8)>]>

QuerySet에서 반환되는 결과는 model의 Meta에서 ordering 옵션에 의해 제공되는 order tuple에 의해 순서가 정해진다. 우리는 order_by() 메소드를 사용해 QuerySet당 기준값을 재정의할 수 있다.

'-' 마이너스 값은 내림차순으로 order 하겠다는 것이며, '?'는 랜덤으로 순서 정하겠다는 뜻이다.

또한, Query expression로 asc(), desc()와 함께 정렬할 수 있다.

14. QuerySet에 제한 걸기(slicing!)

인덱싱으로 QuerySet 수에 제한을 둘 수 있다. 해당 QuerySet과 맞지 않을 객체가 나오면 첫번째는 IndexError, 두번째는 DoesNotExists error가 나올 것이다.

>>> Posting.objects.all()[:3]
<QuerySet [<Posting: Posting object (2)>, <Posting: Posting object (3)>, <Posting: Posting object (4)>]>

15. 한번에 여러 객체 생성하기(bulk_create())

해당 메소드는 객체 리스트를 데이터베이스에 더 효율적으로 insert한다.

>>> Posting.objects.bulk_create([Posting(image_url="https://img.com/lkj90", description="벌크 크리에이트중", username_id=1), Posting(image_url="apple.com/1234", description="New laptopppp", username_id=3)])
[<Posting: Posting object (None)>, <Posting: Posting object (None)>]

mysql을 확인해보면

mysql> mysql> select * from postings;
+----+--------------------------------+----------------------------+-----------------------------------+-------------+
| id | image_url                      | created_at                 | description                       | username_id |
+----+--------------------------------+----------------------------+-----------------------------------+-------------+
|  2 | https://imageurlwillchange.com | 2021-02-05 00:48:07.298620 | NULL                              |           1 |
|  3 | https://shorurl.com/345        | 2021-02-05 01:30:19.752123 | Invalid error must appear         |           1 |
|  4 | https://pleasework.com         | 2021-02-09 11:44:06.806212 | Mouse Mouse Cat Cat Tom and Jerry |           3 |
|  5 | https://image.com/1234         | 2021-02-11 02:50:45.207880 | Heyyyyyy                          |           3 |
|  6 | https://image.com/0987         | 2021-02-11 03:13:24.457341 | Apple Macbook Pro 16inch          |           3 |
|  7 | https://abc.com/123            | 2021-02-13 13:15:16.835500 | 예시입니당                        |           2 |
|  8 | https://image.com/456          | 2021-02-13 13:16:53.448512 | 다음 에시입니당                   |           1 |
| 10 | https://img.com/lkj90          | 2021-02-14 04:10:43.357147 | 벌크 크리에이트중                 |           1 |
| 11 | apple.com/1234                 | 2021-02-14 04:10:43.357215 | New laptopppp                     |           3 |
+----+--------------------------------+----------------------------+-----------------------------------+-------------+
9 rows in set (0.00 sec)

#10, #11으로 들어간것을 확인할 수 있다.

16. 전체값 구하기 (.aggregate())

QuerySet을 통해 django의 집계 함수 module를 딕셔너리 타입으로 반환한다. aggregate() 속 각 argument는 딕셔너리에 포함될 value로 포함된다. 집계도 QuerySet이기 때문에 복잡한 집계를 만들 수 있다. django에서 QuerySet의 특정 필드를 모두 더하거나, 평균, 개수 등을 구할 때 이 메소드를 쓴다.

>>> q = User.objects.aggregate(Count('username'))
>>> print(q)
{'username__count': 6}

>>> a = User.objects.aggregate(members_of_the_group=Count('username'))
>>> print(a)
{'members_of_the_group': 6}

불행히도... 해당 프로젝트에는 숫자가 포함되어있는 값들이 없어 (id값뿐..) Count만 썼다. Sum, Avg, Max, Min, 등 다른 집계함수들을 활용할 수 있다.

17. 필드 추가하기(.annotate())

annotate의 사전적 의미는 주석을 단다는 뜻이다. 하지만 django에서는 주석 대신 field, 즉 칼럼을 추가한다고 생각하면 된다. .annotate()의 각 인수는 QuerySet의 각 객체에 추가 될 주석을 가르킨다. 모델 필드에 대한 참조, QuerySet의 각 객체와 관련된 집계, 단순한 값들을 인수에 넣을 수 있다.

즉, 새로운 필드를 데이터에 추가하고 해당 필드에 어떤 내용을 채울 수 있는 기회를 제공해주는 메소드다. 먼저 데이터 수를 파악하기 위해 .count()라는 함수를 이용해서 '주석'을 달아주고 이른 __count으로 접근할 수 있게 된다!

>>> a = User.objects.annotate(Count('username'))
>>> a[0].email
'cnim@naver.com'
>>> a[0].username__count
1

westagram 데이터베이스가 초라하니... 값 출력이 계속 '1'이거나.. 객체가 하나만 반환된다ㅠㅠ

.aggregate() .annotate()의 차이점은 aggregate은 전체 QuerySet에 대한 값을 계산하고, annotate은 QuerySet의 각 객체들에 작용된다.

Reference:
https://docs.djangoproject.com/en/3.1/ref/models/querysets/
https://docs.djangoproject.com/en/3.1/topics/db/queries/
https://velog.io/@magnoliarfsit/ReDjango-8.-QuerySet-Method-2
갓한아님 블로그 포스트 : https://devvvyang.tistory.com/37?category=973523

profile
Software Developer / 고통은 필연, 괴로움은 선택

2개의 댓글

comment-user-thumbnail
2021년 2월 14일

민지님 정리 너무 깔끔하게 잘하시네요 🤭🤭

1개의 답글