이전에 파이콘 2020 에서 들었던 내용인데
세미나 발표 내용이 개인적으로 알게 된 부분들이 많았었습니다.👍
(김성렬님께 감사합니다🙏)
개인적으로 다시 한번 리마인드 차원에서 블로그에 한번 정리하려고 합니다.
https://www.youtube.com/watch?v=EZgLfDrUlrk&ab_channel=PyConKorea
https://github.com/KimSoungRyoul/Django_ORM_pratice_project/issues/7
from .models import Post
query_set = Post.objects.all() # QuerySet. 아직 SQL 호출안됨.
list(query_set)
from .models import Post
query_set = Post.objects.all() # QuerySet. 아직 SQL 호출안됨. 사용하는 곳 없음.
- ORM은 이후 로직을 알지 못한다.
- 현 시점에 딱 필요한 만큼만 데이터를 가져오려 하기 때문에 비효율적으로 데이터를 가져올 수 있다.
- SQL을 한번만 호출해서 데이터를 재사용할 것
# 불필요하게 SQL이 두번 호출
# 실수 예제
from .models import Post
query_set = Post.objects.all() # QuerySet. 아직 SQL 호출안됨.
first_post = query_set[0] # <Post: Post object (1)>
post_list = list(query_set) # [<Post: Post object (1)>, <Post: Post object (2)>, <Post: Post object (3)>]
1-3 실수예제
를 해결 할 수 있음.from .models import Post
query_set = Post.objects.all() # QuerySet. 아직 SQL 호출안됨.
post_list = list(query_set)
first_post = query_set[0] # QuerySet 캐싱으로 인해서 SQL 호출되지 않음.
# 이미 모든 post_list 에 대한 정보를 가지고 있고
# 0번째 post 를 캐싱해서 가져옴.
(밑에 예제코드를 기준으로 설명)
from .models import Post
posts = Post.objects.all() # QuerySet. 아직 SQL 호출안됨.
# 개발자 입장에서는 각 post의 title 정보가 posts에 담겨 있는 것을 알지만 QuerySet은 모른다.
for post in posts: # 모든 Post 조회함 (SQL 호출: 1)
# QuerySet 입장에서는 post의 title 정보가 필요한 시점은 여기.
# 따라서 title 를 알기위해서 SQL이 for 문을 돌때마다 N번 호출한다. (SQL 호출: N번)
post.title # post.title를 조회할때마다 sql이 계속 호출되는 문제가 발생
역방향 참조 모델
정방향 참조 모델
(
Model.objects
.filter(조건절)
.select_related('정방향 참조 필드') # join으로 가져옴
.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 리스트');
"""
1. 메인모델에 역참조된 모델들을 전부 조회하고 싶다면
단순히 역참조필드만 선언해주면
해당 필드를 조회하기위한 SQL이 1개가 더 수행된다.
(이 예제의 경우는 2개를 선언했기때문에 SQL이 2개 더 추가 수행된다.)
"""
query_set_1 = (
AModel.objects.
prefetch_related(
'b_model_set',
'c_models',
)
)
from djngo.db.models import Prefetch
query_set_2 = (
AModel.objects.
prefetch_related(
Prefetch(to_attr='b_model_set', queryset=BModel.objects.all()),
Prefetch(to_attr='c_models', queryset=CModel.objects.all())
)
)
select * from a_model;
select * from b_model where id in (~~~);
select * from c_model where id in (~~~);
"""
2. 추가적인 조건절을 걸고싶다면 ?
Prefetch() 문법을 사용해서 추가쿼리(셋)을 제어하면된다.
"""python
from djngo.db.models import Prefetch
query_set_2 = (
AModel.objects.
prefetch_related(
Prefetch(to_attr='b_model_set', queryset=BModel.objects.filter(is_deleted=False)),
Prefetch(to_attr='c_model', queryset=CModel.objects.all())
)
)
select * from a_model;
select * from b_model where id in (~~~) and is_deleted is False;
select * from c_model where id in (~~~);
company_queryset = (
Company.objects.
prefatch_related('prodcut_set').
filter(name='company_name1', BModel__name__isnull=False)
)
# SQL: 1번째 호출: Company, filter
select * from "company"
inner join "product"
on ("company"."id" = "product"."product_owned_company_id")
where ("company"."name" = "company_name1"
and "product"."name" IS NOT NULL)
# SQL: 2번째 호출
# prefatch_related
select * from "product"
where "product"."product_owned_company_id" in (1, 43, 75, 23)
queryset = (
Company.objects.
# prefatch_related('product_set').
filter(name='company_name1', product__name__isnull=False)
)
queryset = (
Company.objects.
filter(name='company_name1').
prefatch_related(
'product_set',
Prefetch(queryset=Product.objects.filter(BModel__name__isnull=False))
)
)
company_list = list(Company.objects.prefetch_related("produtct_set".all()) # EagerLoading함
first_company = company_list[0]
first_company.product_set.all() # 여기서는 EagerLoading해서 SQL 발생 안함
first_company.product_set.filter(name="상품명_1") # 하지만 이러면 SQL이 발생됨
# sql 을 발생시키지 않으려면 아래처럼 써야됨.
filtered_product_list = [product for product in first_company.product_set.all() if product.name=="상품명1"]
.select_related()
FilterRelation()
.annotate()
.order_by()
from django.db.models.query import RawQuerySet
from django.db.models import QuerySet
# RawQuerySet
a_model_queryset = (
AModel.objects.
raw(raw_query="""
SELECT *
FROM "order"
INNER JOIN "user" on ("order"."order_owner_id" = "user"."id")
WHERE "user"."username" = %(name_param1)s
"""),
params={"name_param1": "name_test"}
).
prefetch_reladted('product_set_included_order')
)
# 위의 Raw 쿼리셋과 아래 쿼리셋은 동일한 SQL 문을 수행
a_model_queryset = (
AModel.objects.
select_related('order_owner').
filter(order_owner_username='name_test').
prefetch_related('product_set_included_order')
)
company_queryset = Company.objects.filter(id__lte=20).values_list("id", flat=True) # 아직 SQL문 실행안됨
product_queryset = Product.objects.filter(product_owned_company__id__in=company_queryset)
# company_queryset 은 product queryset안으로 들어가는 시점에도 아직 queryset 상태임
# 그래서 product queryset과 company_queryset 이 합쳐져서 수행하게됨.
select * from product
where "product"."product_owned_company_id" in (
select U0."id" from "company" U0
where U0."id" <= 20
);
company_queryset = list(Company.objects.filter(id__lte=20)) # SQL 수행됨.
product_queryset = Product.objects.filter(product_owned_company__id__in=company_queryset)
# 첫번째 SQL
select * from company
where "company"."id" <= 20;
# 두번째 SQL
select * from product
where "product"."product_owned_company_id" in ('첫번째 SQL 실행 결과')
역방향 참조 모델
에서 .exclude() 조건 절 사용시 서브쿼리 발생
정방향참조모델
은 exclude()절에 넣어서 JOIN을 유도하면 의도한SQL수행
values(),values_list() 사용하면 해당 QuerySet에 주어진 select_related(), prefetch_related()옵션들을 전부 무시
values(), values_list()를 사용하면 아래와 같은 DB Raw단위로 데이터를 반환한다
ORM EagerLoading이란 개념의 구현체인 select_related()&prefetch_related()
는 DB Raw단위로 데이터를 조회하는 values() values_list() 에서는 무의미한 옵션