Sparta Coding Club/Today I Learned [TIL]

[TIL] #DAY - 064 - 쿼리셋 공부(1) (QuerySet)

양한마리 2022. 12. 5. 03:12
728x90

 



쿼리셋 관련된 자료를 찾아보다가 너무 잘 정리되어있는 블로그를 봐서 가져와보았다.

내용을 읽어보니 이해가 쏙쏙 되는게 다음에 또 볼려고 이렇게 남겨본다




 

쿼리셋(QuerySet) 이란?

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

from .models import Book
Book.objects.all()	# Book 모델(테이블)의 모든 데이터를 갖고와라!
<QuerySet [<Book: 책 제목1>, <Book: 책 제목2>]>

여기서 objects  Model Manager이다. DB와 Django Model 사이의 Query Operation(질의연산)의 인터페이스 역할을 한다. 이 때, objects 를 사용해서 다수의 데이터를 갖고오는 함수를 사용할 때, 반환되는 객체가 QuerySet이다.

이 Manager 각 모델(클래스)가 최소 하나씩 갖고 있다.
Person.objects 의 의미는, objects라는 이름의 manager가 Person 데이터베이스를 QuerySet 형태로 만들겠다는 의미이다. 그 QuerySet에서 데이터를 검색하게 만들 수 있다.



QuerySet Method 정리하기 (1)

Django Model API에는 기본적으로 제공하는 여러 쿼리 메서드들이 있는데, 자주 사용되는 메서드를 살펴보자.

.all()

  • 테이블 데이터를 전부 갖고온다. 즉, 해당 쿼리셋의 모든 요소를 반환한다. 현재 쿼리셋의 복사본을 반환한다고 볼 수 있다.
  • >>> Person.objects.all() <QuerySet [<Person: 홍길동>, <Person: 임꺽정>]>

.values()

쿼리셋의 내용을 딕셔너리로 각 객체정보를 갖는 리스트 형태로 반환한다.

#1
>>> Blog.objects.filter(name__startswith='Beatles')
<QuerySet [<Blog: Beatles Blog>]>

#2
>>> Blog.objects.filter(name__startswith='Beatles').values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>

1번 코드를 통해 필터링을 해서 하나의 데이터가 추출되었다. 2번 코드는 1번 코드에서 .values() 를 추가했다. 추출된 데이터가 Blog 객체이고 이 객체의 내부 데이터를 딕셔너리 형태로 내보내주는 게 values() 가 한 일이다.

values()는 인자값으로 필드이름을 넘겨서 원하는 필드 정보만 딕셔너리로 구성할 수도 있다.

>>> Blog.objects.values()
<QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>

>>> Blog.objects.values('id', 'name')
<QuerySet [{'id': 1, 'name': 'Beatles Blog'}]>

 

ForeignKey로 설정되어 있는 필드에 대해서는 filed, filed_id 둘 다 같은 결과를 반환한다.

>>> Entry.objects.values('blog')
<QuerySet [{'blog': 1}, ...]>

>>> Entry.objects.values('blog_id')
<QuerySet [{'blog_id': 1}, ...]>

 

filter(), order_by()를 values()와 같이 쓰면 어느 순서든 같은 결과를 반환한다.

Blog.objects.values().order_by('id')
Blog.objects.order_by('id').values()

values()를 먼저 꺼내고 정렬하나, 정렬하고 values()를 꺼내나 같은 결과를 보여준다.

ManyToManyField나 역참조관계에서는 하나의 필드에 여러 데이터가 포함될 수 있기 때문에 values()를 쓰면 너무 많은 결과를 출력할 수 있다.

.filter()

특정 조건에 맞는 Row들을 갖고오기 위해서 사용되는 메소드이다.
괄호안에 lookup parameter(지정된 매개변수)와 일치하는 객체를 포함한 새로운 쿼리셋을 반환한다. 주로 특정 조건에 맞는 데이터를 추출하는데 사용하고 복잡한 쿼리셋을 만들이 위해 Q objects를 사용할 수 있다.

Post.objects.filter(title__startswith='First')
# 제목이 First 로 시작하는 post만 추출한다.

.exclude()

특정 조건을 제외한 나머지 Row들을 갖고오기 위해서 사용되는 메소드이다.
지정된 parameter(매개변수)와 일치하지 않는 객체를 포함한 QuerySet을 반환한다. filter()와 반대로 동작한다.

Post.objects.exclude(title__startswith='First')  
# 제목이 First로 시작하는 post만 제외하고 나머지를 추출한다.
Entry.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline='Hello')

# sql
SELECT ...
WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello')

괄호안에 동시에 조건을 적용하면 AND연산처럼 동작한다. 발행일자가 2005년 1월 3일 이후이면서 headline이 'Hello'인 entry만 제외하고 나머지를 추출한다.

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

# sql
SELECT ...
WHERE NOT pub_date > '2005-1-3'
AND NOT headline = 'Hello'

괄호 밖에 새로 .exclude()를 지정하면 OR연산처럼 작동한다.
발행일자가 2005년 1월 3일 이후이거나 headline이 'Hello'인 모든 항목을 제외하고 추출한다.

.get()

하나의 Row만 갖고오기 위해서 사용되는 메소드이다.
.get()은 쿼리셋이 아니라 모델 객체를 반환하는 메소드이다. 특정 column 조건에 해당하는 결과를 객체로 반환하는 함수이다. 즉, .get()은 쿼리셋을 호출하는 것이 아니라서 뒤에 다른 메소드를 추가할 수 없다.

값은 1개만 불러온다. .filter()와 다른 점은 값이 없을 때 .filter()는 빈 쿼리셋을 불러 오고 .get()은 DoesNotExist라는 메시지를 띄운다. 그리고 해당하는 값이 한 개가 아닐 경우에는 MultipleObjectsReturned라는 메시지를 띄운다. 그러므로 try-except 구문 등으로 예외처리를 해주는 것이 좋다.

# 실제 해당하는 조건의 결과가 없을 경우 발생하는 예외
>>> Account.objects.get(user_account='james')
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/YB/miniconda3/envs/django_tutorial/lib/python3.8/site-packages/django/db/models/manager.py", line 82, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/YB/miniconda3/envs/django_tutorial/lib/python3.8/site-packages/django/db/models/query.py", line 415, in get
    raise self.model.DoesNotExist(
account.models.Account.DoesNotExist: Account matching query does not exist.
# try-except 사용예제
try:
    if Account.objects.filter(user_account=account_data['user_account']).exists():
        account = Account.objects.get(user_account=account_data['user_account'])

        if account.password == account_data['password']:
            return JsonResponse({'message':'Welcome back!'}, status=200)
        return HttpResponse(status=401)

      return HttpResponse(status=400)

    except KeyError:
        return HttpResponse(status=400)

.count()

데이터의 갯수 (row의 수)를 세기 위해 사용되는 메소드이다.
QuerySet과 일치하는 DB 객체의 수를 나타내는 정수를 반환한다.

# 데이터베이스에 있는 entry의 갯수를 반환한다.
Entry.objects.count()

# Returns the number of entries whose headline contains 'Lennon'
Entry.objects.filter(headline__contains='Lennon').count()

.count()는 SELECT COUNT(*) 를 수행하므로, 모든 레코드를 파이썬 객체에 로드하고 len() 를 호출하는 것보다는 count() 를 사용하는게 더 빠르다.(객체를 메모리에 로드하지 않을 때는 len()이 더 빠르다.)

.first()

데이터들 중 처음에 있는 row 만을 반환한다.
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

.last()

데이터들 중 마지막에 있는 row 만을 반환한다.
QuerySet과 일치하는 마지막 객체를 반환하는데, 그외에는 first()와 동일하다.

출처 : https://velog.io/@magnoliarfsit/ReDjango-7.-ORM%EA%B3%BC-Queryset

728x90
반응형