본문 바로가기
Web 개발/Django

QuerySet API (쿼리셋 API)

by yororing 2024. 3. 25.

목차

00 개요

    1. QuerySet이란

01 QuerySet의 evaluate하기

    1. Iteration

    2. Asynchronouns Iteration

    3. Slicing

    4. Pickling/Caching 

    5. repr()

    6. len()

    7. list()

    8. bool()

02 QuerySet API

    0. QuerySet의 공식적인 선언

    1. QuerySet의 공개 속성

    2. 새 QuerySet 반환하는 메서드

        1) filter() 2) exclude() 3) annotate() 4) alias() 5) order_by() 6) reverse() 7) distinct() 8) values() 9) values_list() 10) dates()

        11) datetimes() 12) none() 13) all() 14) union() 15) intersection(), 16) difference() 17) select_related() 18) extra() 19) defer() 20) only()

        21) using() 22) select_for_update() 23) raw()

    3. 새 QuerySet 반환하는 연산자

    4. QuerySet 반환하지 않는 메서드

    5. Field lookups

    6. Aggregation Functions (집계 함수)

    7. Query관련 도구

 

00 개요

  • Django 사용 시 DB에서 객체를 갖고 오려면 모델 클래스의 Manager를 통해 QuerySet을 생성해야 함

1. QuerySet이란

  • DB에서 가져온 객체들의 컬렉션
  • 쉽게 말해, 쿼리를 통해 DB에서 추출해낸 데이터
  • 0개, 1개 또는 여러 개의 필터 가질 수 있음 (필터: 주어진 매개변수에 기반하여 쿼리 결과를 간추리는 데 사용됨)
  • SQL 용어로는 QuerySet = SELECT 문, 필터 = WHERE 또는 LIMIT과 같은 제한절 같은 개념
  • 반환값: 리스트 형태의 쿼리 결과

2. Manager란

  • 모델의 Manager를 사용하여 QuerySet을 얻음
    • 각 모델은 적어도 하나의 Manager를 가지며, 기본적으로 이를 objects라고 함
    • Manager는 모델의 주요 QuerySet 소스로서 다음과 같이 모델 클래스를 통해 직접 액세스 가능:
      >>> Blog.objects
      <django.db.models.manager.Manager object at ...>
      >>> b = Blog(name="Foo", tagline="Bar")
      >>> b.objects
      Traceback:
          ...
      AttributeError: "Manager isn't accessible via Blog instances."
    • 예를 들어, 모델1.objects.all()은 데이터베이스에 있는 모든 모델1 객체를 포함하는 QuerySet을 반환
      >>> session_tb = Session.objects.all()
      >>> session_tb
      <QuerySet [<Session: Session object (session1)>, <Session: Session object (dcvserver202)>]>

01 QuerySet evaluate하기

  • 내부적으로, QuerySet은 실제로 DB에 접근하지 않고도 생성, 필터링, 슬라이싱 및 이곳저곳으로 전달될 수 있음
  • QuerySet을 evaluate하기 위해 어떤 작업을 수행하기 전까지는 실제로 DB 활동이 발생하지 않음
  • QuerySet을 evaluate하는 것은 다음과 같이 가능:
    • Iteration, Asynchronouns Iteration, Slicing ("step" 파라미터 사용), Pickling/Caching, repr(), len(), list(), bool()

1. Iteration (반복)

# DB에 있는 모든 항목의 headline 출력
for e in 모델명.objects.all():
	print(e.headline)

2. Asynchronous Iteration (비동기적 반복)

async for e in 모델명.objects.all():
    results.append(e)
  • QuerySet의 동기 및 비동기 iterators는 둘 다 동일한 기본 캐시를 공유함

3. Slicing (슬라이싱)

  • Python’s array-slicing syntax을 통해 슬라이싱 가능
  • 음수 인덱싱 (i.e. Entry.objects.all()[-1])은 불가능
  • SQL의 LIMIT 및 OFFSET 절의 역할
모델명.objects.all()[:5]		# returns the first 5 objects (LIMIT 5)
모델명.objects.all()[5:10]	# returns the sixth through tenth objects (OFFSET 5 LIMIT 5)
모델명.objects.all()[:10:2]	# returns a list of every second object of the first 10
  • unevaluated QuerySet을 슬라이싱할 때 "step" 파라미터 사용 시에만 evaluated QuerySet 결과 반환
  • "step" 파라미터 미사용 시 unevaluated QuerySet을 반환하기에 adding more filter, modifying ordering과 같은 수정은 더 이상 못함 (이유: SQL로 잘 번역이 안되며 명확한 의미도 없기에)

4. Pickling/Caching

  • Pickle: Python object serialization 모듈
    • pickle 모듈: Python 객체 구조를 직렬화(serialize)하고 역직렬화(de-serialize)하기 위한 binary 프로토콜을 구현함
    • pickling”: Python 객체 계층 구조가 byte stream으로 변환되는 과정
    • unpickling”: pickling의 반대 작업, (바이너리 파일이나 바이트류 객체에서) byte stream을 다시 객체 계층 구조로 변환하는 과정
    • (un)pickling은 “(de-)serialization,” “(un)marshalling,” 또는 “(un)flattening으로도 알려져 있음
  • 중요: pickling/caching의 결괏값은 DB에서 읽어서 갖고 오는 것

5. repr()

  • This is for convenience in the Python interactive interpreter, so you can immediately see your results when using the API interactively

6. len()

  • 반환값: 결과 리스트의 길이

7. list()

model_list = list(모델명.objects.all())

8. bool()

  • boolean context (e.g., bool(), or, and, an if statement 등)에서 QuerySet을 시험할 때 쿼리가 실행될지 안될지 알아봄
  • 하나 이상의 결괏값이 있을 경우 True, 아니면 False
    if 모델명.objects.filter(headline="Test"):
        print("There is at least one Entry with the headline Test")

02 QuerySet API

0. QuerySet의 공식적인 선언:

class QuerySet(model=None, query=None, using=None, hints=None)

1. QuerySet의 공개 속성

    • QuerySet 클래스는 introspection을 사용하기 위한 다음과 같은 공개 속성(public attributes)들이 있음:
      • ordered: QuerySet이 정렬되어 있으면 True (i.e., 모델에 order_by() 절 또는 default ordering이 적용되어 있는 경우), 아니면 False
      • db: 이 쿼리가 지금 실행 시 사용될 DB

2. 새 QuerySet 반환하는 메서드

  • Django는 QuerySet의 결과 유형을 수정하거나 SQL 쿼리가 실행되는 방식을 변경하는 다양한 QuerySet 정제 메서드 제공
  • 종류:
    • filter(), exclude(), annotate(), alias(), order_by(), reverse(), distinct(), values(), values_list(), dates(), datetimes(), none(), all(), union(), intersection(), difference(), select_related(), extra(), defer(), only(), using(), select_for_update(), raw()

1) filter( )

filter(*args, **kwargs)¶
  • 반환값: 주어진 lookup 파라미터(**kargs)에 매칭되는 객체를 갖는 새 QuerySet
  • The lookup parameters (**kwargs) should be in the format described in Field lookups below (원문서에서 찾아보기)
  • (SQL절 관점) AND를 통해 다수의 파라미터가 join 됨
  • 더 복잡한 쿼리를 실행해야 할 경우 (e.g., OR절이 포함된 쿼리) Q 객체(*arg) 사용 권장
  • # This list contains a Blog object
    >>> Blog.objects.filter(name__startswith="Beatles")
    <QuerySet [<Blog: Beatles Blog>]>
    
    # This list contains a dictionary
    >>> Blog.objects.filter(name__startswith="Beatles").values()
    <QuerySet [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}]>

2) exclude()

exclude(*args, **kwargs)
  • 반환값:  주어진 lookup 파라미터(**kargs)에 매칭되지 않는 객체를 갖는 새 QuerySet
  • (SQL절 관점) AND를 통해 다수의 파라미터가 join 되어 있으며 모든 것이 NOT()로 둘러싸여 있음
  • # AND: pub_date가 2005-1-3 이후 AND headline이 "Hello"인 모든 항목 제외
    모델명.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3), headline="Hello")
    
    # OR: pub_date가 2005-1-3 이후 OR headline이 "Hello"인 모든 항목 제외
    모델명.objects.exclude(pub_date__gt=datetime.date(2005, 1, 3)).exclude(headline="Hello")
     
  • # AND SQL 버전
    SELECT ... 
    WHERE NOT (pub_date > '2005-1-3' AND headline = 'Hello')
    
    # OR SQL 버전
    SELECT ... 
    WHERE NOT pub_date > '2005-1-3'
    AND NOT	  headline = 'Hello'​

3) annotate()

annotate(*args, **kwargs)
  • Annotates each object in the QuerySet with the provided list of query expressions
    • An expression may be a simple value, a reference to a field on the model (or any related models), or an aggregate expression (averages, sums, etc.) that has been computed over the objects that are related to the objects in the QuerySet
  • Each argument to annotate() is an annotation that will be added to each object in the QuerySet that is returned
  • The aggregation functions that are provided by Django are described in Aggregation Functions below
  • Annotations specified using keyword arguments will use the keyword as the alias for the annotation. 
  • Anonymous arguments will have an alias generated for them based upon the name of the aggregate function and the model field that is being aggregated. 
  • Only aggregate expressions that reference a single field can be anonymous arguments. 
  • Everything else must be a keyword argument. 
  • For example, if you were manipulating a list of blogs, you may want to determine how many entries have been made in each blog:
    >>> from django.db.models import Count
    >>> q = Blog.objects.annotate(Count("entry"))
    # The name of the first blog
    >>> q[0].name
    'Blogasaurus'
    # The number of entries on the first blog
    >>> q[0].entry__count
    42​
  • The Blog model doesn’t define an entry__count attribute by itself, but by using a keyword argument to specify the aggregate function, you can control the name of the annotation:
    >>> q = Blog.objects.annotate(number_of_entries=Count("entry"))
    # The number of entries on the first blog, using the name provided
    >>> q[0].number_of_entries
    42​

4) alias()

alias(*args, **kwargs)
  • Same as annotate(), but instead of annotating objects in the QuerySet, saves the expression for later reuse with other QuerySet methods. 
  • This is useful when the result of the expression itself is not needed but it is used for filtering, ordering, or as a part of a complex expression. 
  • Not selecting the unused value removes redundant work from the database which should result in better performance. 
  • For example, if you want to find blogs with more than 5 entries, but are not interested in the exact number of entries, you could do this:
    >>> from django.db.models import Count
    >>> blogs = Blog.objects.alias(entries=Count("entry")).filter(entries__gt=5)
  • alias() can be used in conjunction with annotate(), exclude(), filter(), order_by(), and update(). To use aliased expression with other methods (e.g. aggregate()), you must promote it to an annotation:
    Blog.objects.alias(entries=Count("entry")) \
    		.annotate(entries=F("entries"),) \
    		.aggregate(Sum("entries"))
  • filter() and order_by() can take expressions directly, but expression construction and usage often does not happen in the same place (for example, QuerySet method creates expressions, for later use in views). 
  • alias() allows building complex expressions incrementally, possibly spanning multiple methods and modules, refer to the expression parts by their aliases and only use annotate() for the final result.

5) order_by()

order_by(*fields)
  • 모델의 Meta 설정 중 ordering 옵션에 기본값으로 QuerySet의 결괏값이 ordered되어있게 설정되어 있으나 order_by 메소드를 사용하여 order 옵션 재정의 가능
  • 여러 order_by() 사용 시 각 order_by()는 전 order_by()를 무효화 시킴
    • Entry.objects.order_by("headline").order_by("pub_date")
      # 결괏값: headline이 아닌 pub_date로 정렬된 QuerySet
  • 옵션:
    • descending:
      • 필드명 앞에 음수표(-) 붙여주기
        • Entry.objects.filter(pub_date__year=2005).order_by("-pub_date", "headline") 
          # 결괏값: pub_date 내림차순, 후 headline 올림차순
      • desc()
        • Entry.objects.order_by(Coalesce("summary", "headline").desc())
        • asc()와 desc()는 null 값들을 어떻게 정렬할지 지정하는 nulls_first, nulls_last 인자 사용 가능
    • random: "?"
      • 주의: 사용하는 DB 백엔드에 따라 order_by('?') 쿼리들은 비싸고? 느릴 수 있음 
      • Entry.objects.order_by("?")
    • 다른 모델의 필드 참조 시: `필드명__새모델의필드명` (무제한 가능)
      • Entry.objects.order_by("blog__name", "headline")

6) reverse()

  • 반환값: 원래 QuerySet이 reverse된 QuerySet
  • reverse()를 두 번 호출 시 원상태로 복귀
  • 맨 마지막 5 항목 추출시
    my_queryset.reverse()[:5]
    # 결괏값: 맨 마지막 item 먼저, 그 다음 두번째 마지막 item, ... 다섯번째 마지막 item
  • Django에서는 Python sequence의 seq[-5:] (다섯번째 마지막 항목, 4번째 마지막 ... 맨 마지막 항목 추출)와 같은 접근 방법(i.e., slicing from the end) 미제공
    • 이유: it’s not possible to do it efficiently in SQL
  • 일반적으로, 정의된 order가 있는 QuerySet (e.g., 기본값(default)으로 순서가 정해져 있는 모델을 쿼리한 쿼리셋 또는 order_by()를 사용하여 쿼리한 쿼리셋)에만 호출 가능 - 정해진 순서가 없는 경우 reverse() 호출 시 변화 없음

7) distinct()

distinct([*fields])
  • 반환값: 중복된 행을 제거한 새 QuerySet (= SQL 쿼리의 SELECT DISTINCT)
  • 기본값으로 QuerySet은 중복된 행을 제거하지 않음
  • 쿼리가 다수의 테이블을 조회할 경우 QuerySet이 evaluate될 때 중복된 값을 가질 수 있음 - 이 때 distinct() 사용
  • PostgreSQL에서만 positional arguments (*fields) 넣어서 DISTINCT가 적용될 필드(들) 지정 가능 (= SQL 쿼리의  SELECT DISTINCT ON)

8) values()

values([*fields, **expressions])
  • 반환값: iterable로 사용될 경우 모델 인스턴스 대신에 딕셔너리 형태의 QuerySet
    • 각 딕셔너리 = 각 객체
    • 각 키 = 모델 객체의 속성
  • 선택적 인자:
    • *fields: SELECT로 제한해야 하는 필드 이름 지정
      • field 미지정 시 DB 테이블에서 모든 field의 key/value 값들이 포함된 QuerySet 반환
      • field 지정 시 각 딕셔너리는 지정된 field의 key/value만 포함된 QuerySet 반환
  • >>> 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'}]>

9) values_list()

values_list([*fields,] flat=False, named=False)
  • 반환값: iterable로 사용될 경우 모델 인스턴스 대신에 튜플 형태의 QuerySet
    • field 미지정 시 선언된 순으로 모델의 모든 field 값들을 튜플로 반환
    • field/표현식 지정 시 해당 field/표현식 값들을 튜플로 반환
    • 첫 번째 항목 = 첫 번째 필드 값
    • 두 번째 항목 = 두 번째 필드 값, ...
    • >>> Entry.objects.values_list("id", "headline")
      <QuerySet [(1, 'First entry'), ...]>
      
      >>> from django.db.models.functions import Lower
      >>> Entry.objects.values_list("id", Lower("headline"))
      <QuerySet [(1, 'first entry'), ...]>
  • 파라미터:
    • flat: True 설정 시 1-튜플 대신 단일 값 반환
      • 전달된 field가 하나일 경우에만 사용 가능 (여러 field가 전달되었을 경우 flat 사용 시 에러)
      • >>> Entry.objects.values_list("id").order_by("id")
        <QuerySet[(1,), (2,), (3,), ...]>
        
        >>> Entry.objects.values_list("id", flat=True).order_by("id")
        <QuerySet [1, 2, 3, ...]>
    • named: True 설정 시 namedtuple() 반환
      • >>> Entry.objects.values_list("id", "headline", named=True)
        <QuerySet [Row(id=1, headline='First entry'), ...]>
  • 특정한 모델 인스턴스의 특정한 field 값을 추출해야하는 경우 values_list() 와 get()을 함께 사용
    • >>> Entry.objects.values_list("headline", flat=True).get(pk=1)
      'First entry'

10) dates()

11) datetimes()

12) none()

13) all()

14) union()

15) intersection()

16) difference()

17) select_related()

18) prefetch_related()

19) extra()

20) defer()

21) only()

22) using()

23) select_for_update()

24) raw()

 

 

 

참조

  1. https://docs.djangoproject.com/en/5.0/topics/db/queries/
  2. https://docs.djangoproject.com/en/5.0/ref/models/querysets/#when-querysets-are-evaluated
  3. https://docs.djangoproject.com/en/5.0/topics/db/queries/#limiting-querysets
  4. https://docs.djangoproject.com/en/5.0/ref/models/querysets/#pickling-querysets
  5. https://docs.python.org/3/library/pickle.html#module-pickle
  6.