(Django) select_related 와 prefetch_related를 사용한 데이터 참조

Kepler·2020년 2월 23일
23

Django

목록 보기
10/12

언제 이 두가지를 사용해야 할까?

The only reason to use either of these methods is when a single large query is preferable to many small queries. Django uses the large query to create models in memory preemptively rather than performing on demand queries against the database.

셀렉트할 객체가 역참조하는 single object(one-to-one or many-to-one)이거나, 또는 정참조 foreign key 일 때 사용한다.

You can use related_name in the linking model for backward reference.

We use select_related when the object that you're going to select is a single object, which means forward ForeignKey, OneToOne and backward OneToOne.

select_related는 각각의 lookup마다 SQL의JOIN을 실행하여 테이블의 일부를 가져오고, select .. from에서 관련된 필드들을 가져온다.

select_related works by creating an SQL join and including the fields of the related object in the SELECT statement. For this reason, select_related gets the related objects in the same database query.

..select_related does an SQL join and therefore gets the results back as part of the table from the SQL server.

형식:

  • parameter에는 참조하는 class의 이름을 소문자로 쓰고 ' '에 감싼다.
  • 여러개의 parameter를 가질 수 있다.
    예) Store.objects.filter(id=1).select_related('gungu','city').values('id','name','city_name')

예제: selected_related를 사용했을 때와 사용하지 않았을 때의 query 방식에 대해서 살펴보자.

다음 모델의 관계는 child인DrinkSubCategoryCategory를 parents 로 가지는 역참조관계이며, 하나의 Drink는 하나의 SubCategoryCategory를 가지는 many-to-one의 관계이다.

🎯 는 데이터베이스를 히트할때를 의미한다.

@ manage.py shell
>>> from drink.models import *

# select_related를 사용하지 않은 경우 
>>> drink = Drink.objects.get(id=1) 🎯                                [1]
>>> drink
<Drink: dolce cold brew>
>>> SubCategory.objects.filter(id=drink.sub_category_id)              [2]
<QuerySet [<SubCategory: cold brew>]>
>>> SubCategory.objects.filter(id=drink.sub_category_id)[0] 🎯        [3]
<SubCategory: cold brew> 
>>> SubCategory.objects.filter(id=drink1.sub_category_id)[0].name 🎯  [4]
'cold brew'

# selected_related를 사용한 경우
>>> drink_sr = Drink.objects.select_related('sub_category').get(id=1) 🎯   [a]
>>> drink_sr
<Drink: dolce cold brew>
>>> drink_sr.sub_category.name                                               [b]
'cold brew'

drink객체를 지정할때와, 지정한 객체의 sub_category_id를 불러올때, 총 두 번 DB를 hit한다.

[1]: drink변수에 id=2인 Drink객체를 지정한다.
[2]: 리턴하고 싶은 object를 머릿말에 지정한다. 이 경우는 SubCategory중에서, drink객체가 가진 sub_category_id만을 필터하여 그 쿼리셋을 리턴한다.
[3]: [2]의 결과과 쿼리셋이었으므로, 객체에 접근하기 위해 [ ]를 사용하여 인덱스를 지정한다.
[4]: 객체가 되었으므로, dot notation을 사용하여 name을 구한다.

drink객체를 지정할때, 이미 그의 관계되는(related) sub_category의 정보들을 불러왔으므로, 한 번 DB를 hit한다.

[a]: select_related를 사용하여, Drink의 필드인 sub_category를 통해, SubCategory에 바로 접근하며, 동시에 get으로 id=1인 Drink를 가져온다.
[b]: 객체이므로, 곧바로 dot notation을 사용하여 SubCategoryname을 구한다.

구하려는 객체가 정참조 multiple objects(many-to-many or one-to-many)이거나, 또는 역참조 Foreign Key일때 사용한다.

We use prefetch_related when we’re going to get a set of things.
That means forward ManyToMany and backward ManyToMany, ForeignKey.

selected_related와 달리, prefetch_related는 SQL의 JOIN을 실행하지 않고, python에서 joining을 실행한다.

prefetch_related does a separate lookup for each relationship, and performs the “joining” in Python.
It is different from select_related, the prefetch_related made the JOIN using Python rather than in the database.

첫번째 모델에서 쿼리를 실행하면서, prefetch에 필요한 모든 IDs를 수집하고, SQL의 WHERE IN(IDs)의 쿼리를 실행한다.

SELECT "credential"."id",
       "credential"."uuid",
       "credential"."identity_id"
FROM   "credential"
WHERE  "credential"."identity_id" IN
    (84706, 48746, 871441, 84713, 76492, 84621, 51472);

(SQL WHERE IN: https://www.dofactory.com/sql/where-in)

Rather than performing a single join with potentially too many rows, each table is split into a separate query.

형식:

  • <lowercase 모델이름>_set : 장고가 지어주는 default 역참조 메서드 이름. ForeignKey 필드에 이름을 정하면 default이름 대신 given 이름을 쓸 수 있다.
class Profile(models.Model):
    group = models.ForeignKey(Group, related_name='profiles')
    
# Now, you can access the foreign key as follows:
group.profiles.all()

(Source: https://stackoverflow.com/questions/17328910/django-what-is-reverse-relationship)

예제:

>>> drink2 = SubCategory.objects.filter(id=1).prefetch_related("drink_set")      [1]
<QuerySet [<SubCategory: cold brew>]>

>>> drink2[0]                               					 [2]
<SubCategory: cold brew>

>>> drink2[0].drink_set.filter(id=1)						 [3]
<QuerySet [<Drink: dolce cold brew>]>

>>> drink2[0].drink_set.get(id=1).name_en					 [4]
'dolce cold brew'

[1]. SubCategory중에서 id=1인 SubCategorydrink_set을 불러오면 해당 id를 가진 Drink들이 쿼리셋으로 리턴된다.
[2]. [ ]를 사용하여, 0번째 인덱스를 불러오면, 하나의 객체로 리턴된다.
[3]. 객체에 다시 필터를 적용하여, id=1인 Drink를 가져온다.
[4]. Drink의 필드인 name_en에도 접근이 가능하다.

Understanding ForeignKey

parent&child의 관계를 생각하면 이해하기가 한층 쉬워진다.

A parent(mother) can have many children.
A child can have only one parent(mother).

  • ForeignKey는 child에 지정하는것이 좋다.

  • many-to-many관계에서 ForeignKey는 lower level에 지정하는 것이 좋다.
    예) class Movie and class Character. One movie can have many characters and many characters can be in one movie. In such case, Character should have a FK.

  • ForeignKey를 가진 child는 lookup(__)을 사용하여, parent의 필드를 참조할 수 있다.

>>> Drink.objects.filter(sub_category__name='cold brew')
<QuerySet [<Drink: dolce cold brew>, <Drink: cold brew>]>
  • parent도 같은 방법으로 자신의 child의 필드를 참조할 수 있다.
    • 이때, child class의 이름은 lowercase로 작성하자.
>>> SubCategory.objects.filter(drink__calories__gt=300)
<QuerySet [<SubCategory: cold brew>, <SubCategory: cold brew>]>

ForeignKey와 정참조와 역참조

  • FK를 가진 클래스에서 가지지 않는 클래스를 참조할 때는 정참조
  • FK를 가지지 않은 클래스에서 가진 클래스를 참조할 때는 역참조
class ModelA(models.Model):
    pass

class ModelB(models.Model):
    a = ForeignKey(ModelA)

ModelB.objects.select_related('a').all() # Forward ForeignKey relationship
ModelA.objects.prefetch_related('modelb_set').all() # Reverse ForeignKey relationship

(Code, Explanation: https://stackoverflow.com/questions/31237042/whats-the-difference-between-select-related-and-prefetch-related-in-django-orm)


참고링크:

profile
🔰

1개의 댓글

comment-user-thumbnail
2021년 1월 7일

안녕하세요.
글 잘 읽었습니다.

답글 달기