Django/Django Vanila

21. Django Queryset 활용(select_related, prefetch_related)

S.T.Lee 2022. 6. 15. 22:07

참고 Django 공식 문서 - https://docs.djangoproject.com/en/4.0/ref/models/querysets/

 

QuerySet API reference | Django documentation | Django

Django The web framework for perfectionists with deadlines. Overview Download Documentation News Community Code Issues About ♥ Donate

docs.djangoproject.com

select_related()

select_related(*fields)

Returns a QuerySet that will “follow” foreign-key relationships, selecting additional related-object data when it executes its query. This is a performance booster which results in a single more complex query but means later use of foreign-key relationships won’t require database queries.

 

대략적으로 select_related는 foregin-key 관계에 있는 QureySet을 돌려주며 연관되어 있는 object  데이터를 선택한다. 이것은 더 빠른 결과를 보여주고 db에 접속하는 횟수를 줄여준다.

 

아래는 Django 공식 문서의 예시이다.

The following examples illustrate the difference between plain lookups and select_related() lookups. Here’s standard lookup:

# Hits the database.
e = Entry.objects.get(id=5)

# Hits the database again to get the related Blog object.
b = e.blog

And here's select_related lookup:

# Hits the database.
e = Entry.objects.select_related('blog').get(id=5)

# Doesn't hit the database, because e.blog has been prepopulated
# in the previous query.
b = e.blog

상단의 예시처럼 select_related를 사용하면 단 한번만 db에 접속함으로써 데이터를 받을 수 있다.

1번의 경우, id=5인 object를 가져오면서 한번 object에서 blog라는 항목을 찾으면서 한번, 총 2번의 db 접속을 한다.

2번의 경우, id=5인 object를 가져오면서 blog에 대한 정보를 가져와서 단 한번의 db 접속을 한다.

 

다음은 필자가 사용한 코드인데

class Store(models.Model):
    class Meta:
        db_table = "store"

    name = models.CharField(max_length=20)
    location = models.CharField(max_length=100)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    url = models.URLField(max_length=200, null=True)

class Review(models.Model):
    class Meta:
        db_table = "review"

    store = models.ForeignKey(Store, on_delete=models.CASCADE)
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    star = models.FloatField(validators=[MinValueValidator(0), MaxValueValidator(5)])
    calc_star = models.FloatField(validators=[MinValueValidator(0), MaxValueValidator(100)], null=True)
    comment = models.CharField(max_length=300)
    create_date = models.DateTimeField(null = True)
    update_date = models.DateTimeField(auto_now=True)

 특정 기간의 상점의 리뷰를 가져오고자 한다.

filterStore = Review.objects.filter(create_date__gte=datetime(2022, 6, 4),
                                        create_date__lte=datetime(2022, 6, 10)).select_related('store').all()

이렇게 하면 db에 한번 접속함으로써 리뷰, 가게의 정보를 가져올 수 있다.

 

 

prefetch_related()

prefetch_related(*lookups)

Returns a QuerySet that will automatically retrieve, in a single batch, related objects for each of the specified lookups.

This has a similar purpose to select_related, in that both are designed to stop the deluge of database queries that is caused by accessing related objects, but the strategy is quite different.

 

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. However, to avoid the much larger result set that would result from joining across a ‘many’ relationship, select_related is limited to single-valued relationships - foreign key and one-to-one.

 

prefetch_related, on the other hand, does a separate lookup for each relationship, and does the ‘joining’ in Python. This allows it to prefetch many-to-many and many-to-one objects, which cannot be done using select_related, in addition to the foreign key and one-to-one relationships that are supported by select_related. It also supports prefetching of GenericRelation and GenericForeignKey, however, it must be restricted to a homogeneous set of results. For example, prefetching objects referenced by a GenericForeignKey is only supported if the query is restricted to one ContentType.

 

QuerySet을 한번의 db접속으로 돌려준다. select_related와 비슷하다.

select_related는 SQL에서 join을 하여 같은 db의 쿼리에서 연관된 객체를 얻는다. 또한 foreign key, one-to-one 관계에서만 가능하다.

prefect_related의 경우 파이썬에서 join을 한다. 이는 select_related와 다르게 모든 관계에서 가능하게 해준다.

 

catMatchStore = Store.objects.all().filter(category=Category.objects.get(name=category))
cms = catMatchStore.annotate(average=Avg('review__star'))

cms = cms.prefetch_related(
    Prefetch(
        'review_set',
        queryset=Review.objects.all(),
        to_attr='reviews'
    )
)

Store object에서 store 하나당 review를 가지고 오고 싶을때 사용하였다.

reveiw_set을 통해 store와 review를 join하였고 이를 이제 Store.objects.get(id=n).reviews를 통해 store의 review를 볼 수 있다.