21. Django Queryset 활용(select_related, prefetch_related)
참고 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를 볼 수 있다.