Django QuerySet API

seyong·2021년 12월 19일
0
post-thumbnail

Query Set

쿼리(query)란 데이터베이스에 정보를 요청해주는 것이다.
이 때 파이썬으로 작성한 코드가 SQL로 mapping되어 쿼리셋(queryset) 이라는 자료 형태로 값이 넘어오게 된다

Query set 이란?

  • Database에서 전달받은 모델의 객체 목록이다.

  • Query set은 데이터베이스로부터 데이터를 읽고, 필터를 걸거나 정렬을 하거나 할 수 있다.

  • 쿼리셋은 순회가 가능한 데이터로써 1개 이상의 데이터를 불러와 사용할 수 있다.

  • 쿼리셋의 특징 중 하나는 바로 lazy하다는 점인데, 쿼리셋은 미리 데이터베이스에 접근해서 값을 불러오는 것이 아니라 출력 등과 같이 필요한 순간에 sql로 매핑되어 데이터베이스에 접근해 값을 가져온다.

QuerySet method을 실행했을 때 쿼리셋을 반환하거나 그렇지 않은 경우도 있다.





QuerySet Method 종류

all() , filter() , exclude() , values() , values_list() , get() , create() , count() , exists() , update() , delete() , first() , last() ..

QuerySet을 반환하는 경우

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

QuerySet을 반환하지 않는 경우

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




✔ QuerySet을 반환할때 사용하는 method

먼저 예시를 보여드리기전에 모델하나를 정의하겠습니다.

Model


class Category(models.Model):
    name = models.CharField(max_length=50)

    class Meta:
        db_table = 'categories'

all()

  • 원하는 테이블의 모든 객체를 가져온다.
  • 그 결과로 쿼리셋을 반환하는데, 이 때 쿼리셋 안에는 각각 인스턴스가 포함되어 있다.
  • 인스턴스들이 담겨 있는 QuerySet이 반환되기 때문에, 모든 속성에 접근해서 데이터를 관리할 수 있다.

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(**kwargs)를 사용하면 키워드 인자로 주어진 lookup 조건에 일치하는 레코드들의 QuerySet을 반환한다.
# case 1
In  : Category.objects.filter(name='콜드브루')
Out : [<Category: Category object (3)>, <Category: Category object (4)>]

# case 2
In  : Category.objects.filter(name='콜드브루').filter(id=3)
Out : [<Category: Category object (3)>]

exclude()

  • 조건을 여러개 주면 그 조건을 전부 만족하는 것만 빼고 나머지를 반환한다.
  • lookup 조건에 맞지 않는 객체를 반환한다.
In  : Category.objects.filter(name='콜드브루').exclude(id=3)
Out : [<Category: Category object (4)>]

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을 반환하지 않을 때 사용하는 method

create()

  • Table에 데이터를 추가(INSERT) 해주는 method로, 생성된 인스턴스를 반환해준다.
In  : Category.objects.create(name='콜드브루')
Out : <Category: Category object (1)>

#category 변수에 반환된 값을 저장하고, 생성된 data를 사용할 수 있다.

#인스턴스로 반환되므로 category.name으로 class 안에 변수에 접근할 수 있다.

In  : category = Category.objects.create(name='콜드브루')
In  : category.name
Out : '콜드브루'

# 참고) save method : INSERT 또는 UPDATE
# Category(name='콜드브루').save()

get()

  • 지정된 조회 매개 변수와 일치하는 인스턴스를 반환한다.이 매개 변수는 필드 조회에 설명 된 형식이어야한다.

  • 결과가 1개인 값을 명확하게 가져올 때 사용한다.

  • 0개 또는 2개이상의 결과를 가지면 에러가 발생된다.(filter와 다름)

In  : Category.objects.get(id=1)
Out : <Category: Category object (1)>

update()

  • 지정된 필드에 대해 업데이트 쿼리를 수행하고 일치하는 행 수를 반환한다.
    (일부 행에 이미 새 값이있는 경우 업데이트 된 행 수와 같지 않을 수 있음).

  • 수정한 row의 개수를 반환한다.

  • 여러 개의 객체가 든 QuerySet에다 update를 주면 한꺼번에 수정할 수 있다.

🔼 categories 테이블안에있는 기존 값들 (select * from categories)

In  : Category.objects.filter(name='탄산').update(name='콜드브루')
Out : 2  #총 업데이트된 row 개수

🔼 update()를 하고 난 후, categories 테이블안에있는 값들 (select * from categories)

'탄산' 이라는 name을 가진 값들이 update(name='콜드브루') 로 인하여 name 값이 '콜드브루' 바뀐 것을 볼 수 있다.

delete()

  • QuerySet의 모든 행에 대해 SQL 삭제 쿼리를 수행하고 삭제 된 개체 수와 개체 유형별 삭제 횟수가 있는 dictionary를 반환한다.

  • instance로 가져와서 delete()해도 삭제가 된다.

  • 여러 개의 객체가 든 QuerySet에다 delete를 주면 한꺼번에 수정할 수 있다.

위에 콜드브루로 update()한 이미지를 참고하여 id=1 이고 name = qp 인 값을 delete() 해보겠다.


In  : Category.objects.filter(name='qp').delete()
Out : (1, {'products.Category': 1})

🔼 id=1 값이 삭제 된 것을 볼 수 있다.

save()

  • INSERT 또는 UPDATE 를 수행하는 method로, 단일 객체에 대해서 업데이트를 수행할 때 많이 사용된다.

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

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

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

In  : category.name
Out : 'new name'

exists()

  • filter()와 함께 서용해서 filter 조건에 맞는 데이터가 있는지 조회, 존재하면 True 존재하지 않으면 False를 반환한다.

  • list, object 등에다 붙여 쓸 수 없다.


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

get_or_create()

  • object와 boolean을 담은 Tuple 을 반환한다.

  • 기존에 있어서 수정했으면 (object, False),
    새로 생성하면 (object, True)
    조건대로 찾아서 없으면 defaults dictionary의 값을 바탕으로 생성 하기 때문에 여기엔 필수 column 정보를 다 입력해야 된다.

  • get으로 조회하기 때문에 반드시 결과가 하나인 <찾을 column명>=<찾을 값> 조건을 주어야 된다.

아래는 예제코드이다.


obj, created = Person.objects.get_or_create(first_name='John', last_name='Lennon', defaults={'birthdate': date(1995, 04, 28)})

bulk_create()

  • shell에서 실행한 결과로는 object의 id가 None으로 나온다. DB엔 잘 들어가 있다.

  • batch_size argument도 있다. batch_size는 한 쿼리당 몇개의 object를 넣을지 설정하는 역할. default는 None이라 주어진 걸 한 번에 다 넣는다.

  • () 안에는 넣을 object들을 요소로 갖는 list 가 들어간다.

  • 다수의 레코드를 생성할 때 한번의 커넥션 만으로 insert를 수행할 수 있다.


users = User.objects.all()

# notification 오브젝트 리스트를 만든다.
new_noti_list = [Notification(user=user, contents="반갑습니다.") for user in users]

Notification.objects.bulk_create(new_noti_list)

Bulk_create 사용시 주의할 점

Bulk_create는 매우 편리한 기능이지만 사용할 때 꼭 주의해야 할 점이 있다. 바로 bulk 를 사용하면 Django Model 클래스에서 제공 하는 기본 메소드(save, clean... 등) 들을 사용하지 못한다는 점이다. 어떤 모델은 save(), clean() 메소드 등을 오버라이드 하여 오브젝트 저장 시점에 특별한 액션(트랜잭션, 유효성 검사 등)을 취할 수 있다. 하지만 bulk 를 사용하면 오브젝트를 DB에 직접 때려박는 식이기 때문에 모델 클래스의 기본 메소드 지원을 받을 수 없다. 그말은 즉 bulk operation을 통해 데이터 베이스에 유효하지 못한 값이 들어가거나 데이터의 무결성이 깨질 가능성이 생긴다는 것이다.

따라서 bulk를 사용하기 전 해당 모델의 생성에 의해 영향을 받는 모델이 있는지, 모든 필드에 유효한 값이 들어오는지 등의 사전확인을 철저히 해야 한다.

count()

  • 데이터의 갯수(row 수)를 세는 메서드이다.

  • QuerySet 안에 들어있는 object의 개수를 반환한다.


queryset1 = Model.objects.all()
count = queryset1.count()

first()

  • QuerySet의 첫번째 object를 반환한다.
    없으면 None을 반환한다.

  • QuerySet이 정렬된 게 아니라면 pk 순으로 정렬한다.

  • 데이타들 중 처음에 있는 row만을 리턴한다. 아래는 name필드로 정렬했을 때 처음 row를 리턴한다.


rows = Feedback.objects.order_by('name').first()

last()

  • QuerySet의 마지막 object를 반환한다.
    그 외는 first()와 같다.
  • 데이터들 중 마지막에 있는 row만을 리턴한다. 아래는 name필드로 정렬했을 때 마지막 row를 리턴한다.

rows = Feedback.objects.order_by('name').last()

위의 쿼리 메서드들은 하나 하나가 실제 데이타 결과를 직접 리턴한다기 보다는 쿼리 표현식(Django에서 QuerySet이라 한다)을 리턴하는데, 여러 메서드들을 체인처럼 연결하여 사용할 수 있다. 즉, 여러 체인으로 연결되어 리턴된 쿼리가 해석되어 DB에 실제 하나의 쿼리를 보내게 된다. 아래는 여러 메서드들을 사용하여 체인으로 연결한 예제이다.

	
row = Feedback.objects.filter(name='Kim').order_by('-id').first()
# Feedbakc테이블의 name이 `kim`인 값을 필터링하고 그것을 -id 순으로 정렬한것중에서, 그 중 첫번째 것을 row라는 변수에 객체로 담아준다.

# order_by = 조건에 따라 정렬된 QuerySet을 반환

aggregate()

  • Django 에서는 필드 전체의 합, 평균, 개수 등을 계산할 때 사용한다.

  • 모든 컬럼을 합쳐주는 기능을 한다.

from django.db.models import Avg, Max, Min

>>> Point.objects.all().aggregate(Max('saved_point'))
{'saved_point__max': 313500}

>>> Point.objects.all().aggregate(Min('saved_point'))
{'saved_point__min': 313}

>>> Point.objects.all().aggregate(Avg('saved_point'))
{'saved_point__avg': 112536.5625

annotate()

  • 사전적 의미는 '주석을 달다'인데, Django에서는 주석 대신 '필드'를 추가한다고 생각하면 편하다. (엑셀이라면 컬럼을 추가하는 셈.)

  • 필드 하나를 만들고 거기에 '어떤 내용' 을 채우게 만드는 것

  • 다른 필드의 값을 그대로 복사하거나, 다른 필드의 값들을 조합한 값을 넣을 수 있다.

annotate()는 해당 블로그 글에 자세히 설명되어있으니
참조하는것이 훨씬 이해가 빠를것이다.



.Reference

http://pythonstudy.xyz/python/article/310-Django-%EB%AA%A8%EB%8D%B8-API
http://raccoonyy.github.io/django-annotate-and-aggregate-like-as-excel/

profile
# 불편함을 편리함으로 바꾸고싶은 주니어 Back-end 개발자

0개의 댓글