데이터베이스에서 전달 받은 객체 목록
이다. 데이터 타입은 파이썬에서 제공되는 기본형이 아니고 복잡한 자료구조로 전달된다. 그래서 queryset
이라고 명명하게 되었다. 후에 해당 자료구조는 JSON
이나 dictionary
형태로 가공되어 제공되는 것이 일반적이다.
queryset
은 한개의 쿼리와 N개의 추가 쿼리셋으로 구성되어있다.
Model
이 DB에서의 테이블
이라면 Model Instance
는 테이블에서 셀렉트 한 하나의 레코드
를 일컫는다.
queryset
의 특징이 있다. queryset
은 불필요한 query
(select문)를 날리지 않는다는 점이다. 예를 들어 아래와 같은 코드가 있을 때, 1번 시점은 아직 query
를 날리기 전이다. users가 의미하는 것은 모델(테이블)에서 전달받은 객체 목록이다. 아직 해당 테이블에 query
를 날리기 전이다.
2번 시점에서 ORM이 query
를 날린다. 3번 지점에서 또 한번 query
를 날리게 된다. 이렇게 ORM이 쿼리를 2번 날리는 이유는 2번 시점에서 데이터베이스에 히트를 칠 때 가지고 온 값이 하나의 User 모델의 인스턴스만 가지고 오기 때문이다(ORM의 lazyloading)
.
ex1)
users = User.objects.all() #1
User = users[0] #2
user_list = list(users) #3
자 이번에는 한문장 한문장은 위와 같지만 순서가 다르다. 하지만 이 차이점으로 인해 총 한번의 쿼리를 수행한다. 왜냐하면 2번 시점에서 디비에 접근할 때 가지고 온 값이 3번을 포함하고 있어 다시 쿼리를 날리지 않아도 되기 때문이다.
ex2)
users = User.objects.all() #1
user_list = list(users) #2
User = users[0] #3
ORM의 Lazy Loading이라는 특징 그리고 캐싱이라는 특징을 잘 숙지하고 ORM을 작성 할 필요성이 있다.
User 테이블에서 Post(게시물)에 대한 객체를 왜래키로 들고 있다고 했을 때 아래와 같이 코드를 작성하게 될 경우 총 1+N
의 쿼리를 발생시키게 된다. 왜냐하면 ORM은 필요 한 만큼만 query를 날리기 때문이다.
users = User.objects.all()
for user in users:
user.userinfo
equals to
#1
select * from User;
#2
select * from UserInfo
where UserInfo.id = 1;
#3
select * from UserInfo
where UserInfo.id = 2;
#4
select * from UserInfo
where UserInfo.id = 3;
...
...
...
이에 N+1문제를 해결하기 위해서 Django ORM 에선prefetch_related('UserInfo')
를 통해 필요한 항목을 한번의 쿼리로 한 번에 가지고 올 수 있다. #1 == #2
#1
User.objects.prefetch_related('UserInfo')
#2
select * from UserInfo where UserInfo.id in [1,2,3 ... n]
ORM에서 한 번에 많은 데이터를 가지고 오고 싶을 때 이를 Eager Loading 이라고 합니다. Django ORM에선 prefetch_related
select_related
메서드를 제공한다.
_result_cache
는 Django에서 구현된 Queryset
객체 내에 있는 멤버 변수이다. Queryset
재호출시 해당 변수에 저장된 값이 없으면 query
를 수행한다.
join
을 통해서 데이터를 로딩한다.
역방향참조모델
은 select_related에 줄 수 없다.
select_related('정방향_참조_필드')
해당 메서드의 경우 역방향참조필드를 줄 수 없다.
예시를 통해 살펴보자. 아래 Django ORM은 아래 SQL문과 그 의미가 같다. OrderProduct라는 테이블이 두개의 테이블을 정방향 참조하고 있다고 가정했을 때 select_related 사용이 가능하다.
SQL문을 보면 알 수 있겠지만 OrderProduct
테이블과 공통된 필드가 있는 부분을 RelatedOrder
테이블과 RelatedProduct
테이블에서 가지고 온 후 그 중 OrderProduct
의 아이디가 4인 값을 가지고 와서 전체를 보여주는 SQL 구문이다.
#예시
OrderProduct.select_related('related_order','related_product')
.filter(related_order=4)
equals to
select * from OrderProduct OP
inner join RelatedOrder on (OP.related_order_id = RelatedOrder.id)
inner join RelatedProduct on (OP.related_order_id = RelatedProduct.id)
where OP.related_order_id=4;
Inner Outer 여부는
QuerySet조건절 변경에 따라 JOIN 옵션이 변할수있다
하지만 일반적으로 ForiegnKey(null=True) 이면 OUTER JOIN
select * from model m
inner OR outer join '정방향참조필드' r on (m.r_id = r.id)
where '조건절';
추가쿼리
를 사용해 데이터를 가지고 온다.
역방향참조필드
정방향참조필드
모두 사용 가능하나 역방향참조필드
를 사용 할 것을 권장함.
쿼리셋은 한개의 쿼리와 N개의 추가쿼리로 이루어져있다. 무슨 말인가 하면 prefetch_related
라는 ORM을 사용했을 때 prefetch의 대상이 되는 모델에 대해서 쿼리를 한번 날리고 prefetch_related('A')
처럼 그 안에 해당되는 모델(A)에 대해서 추가쿼리
를 수행하기 때문에 한개의 쿼리와 N개의 추가쿼리로 이루어졌다는 말이 등장하게 된다.
#1
prefetch_related('역방향_참조_필드')
prefetch_related('A', 'B', 'C')
>>> select * from A where id in = [...]
>>> select * from B where id in = [...]
>>> select * from C where id in = [...]
#2
AModel.objects.prefetch_related("b_model_set", "c_models")
equals to
AModel.objects.prefetch_related(
Prefetch(to_attr="b_model_set"), queryset=BModel.objects.all())
Prefetch(to_attr="c_models"), CModel.objects.all())
)
equals to
select * from A;
select * from B where B.id in [...];
select * from C where C.id in [...];
위에서 `queryset=BModel.objects.all()`이라는 부문은
queryset=BModel.objects.filter(is_delete=False) 와 같이 customizing 할 수 있다.
equals to
select * from A;
select * from B where B.id in [...] and B.is_delete=False;
select * from C where C.id in [...];
Model.objects.filter(조건절).select_related('정방향_참조_필드')
.prefetch_related('역방향_참조_필드')
select * from Model m
(inner or left outer) join '정방향_참조_필드' r on m.r_id = r.id where = '조건절';
select * from '역방향_참조_필드' where id in ('첫번 째 결과 id 리스트');
Model.objects.filter(조건절).select_related('정방향_참조_필드')
.prefetch_related('역방향_참조_필드를 가지고 있는 참조필드')
select * from Model m
(inner or left outer) join '정방향_참조_필드' r on m.r_id = r.id where = '조건절';
select * from '참조필드' a
(inner or left outer) join '참조필드가 참조하고 있는 역뱡향 참조필드' b on (a.b_id = b.id)
where id in ('첫번 째 결과 id 리스트');
#3
OrderProduct.objects.filter(product_cnt__lt=30, related_order__descrption='*')
.prefetch_related(
Prefetch('related_order',
queryset=Order.objects.select_related('mileage').all() )
)
)
*** equals to ***
select * from OrderProduct OP
inner join RelatedOrder RO on (OP.related_order_id = RO.id)
where OP.product_cnt < 30;
select * from RelatedOrder RO
left outer join Mileage M on (RO.id = M.related_order_id)
where RO.id in = [위 query id 결과]
###
User.objects.annotate(first=Substr("first_name", 1, 1),
last=Substr("last_name", 1, 1)).filter(first=F("last"))
queryset = Post.objects.values()
print(str(queryset.query))
SELECT "blog_post"."id", "blog_post"."author_id",
"blog_post"."title", "blog_post"."text",
"blog_post"."created_date", "blog_post"."published_date"
FROM "blog_post"
#1
queryset = Post.objects.annotate(ti=F('title'), te=F('text'))
print(str(queryset.query))
SELECT "blog_post"."id", "blog_post"."author_id",
"blog_post"."title", "blog_post"."text",
"blog_post"."created_date", "blog_post"."published_date",
"blog_post"."title" AS "ti", "blog_post"."text" AS "te" FROM "blog_post"
#2
queryset = Post.objects.annotate(ti=F('title'), te=F('text')).values('ti','title')
print(str(queryset.query))
SELECT "blog_post"."title" AS "ti", "blog_post"."text" AS "te" FROM "blog_post"
디비 내부 특정 모델에 대한 쿼리셋에 대해 계산을 한다.
디비 내부 특정 모델에 안의 레코드에 대해 계산한다.
참고자료