Open4
django rest frameworkで個人プロジェクトを作る過程
queryの最適化?
djangoではormを扱うときselect_related
とprefetch_related
でjoinとかqueryのcustomizeができる
threadを呼び出すときqueryのcustom
- 最初
def get_queryset(self):
user = self.kwargs.get('username')
thread_id = self.kwargs.get('pk')
qs = self.queryset.filter(id=thread_id)
return qs
結果
queryの量も多いしchild objectが増えるたびにqueryの数が多くなってしまう
- 改善
ここでselect_related
とprefetch_related
を使えば
def get_queryset(self):
user = self.kwargs.get('username')
thread_id = self.kwargs.get('pk')
prefetches = [
Prefetch('comments', queryset=Comment.objects.select_related('user').prefetch_related('replies').order_by(
'-updated_at')),
Prefetch('likes')
]
qs = self.queryset.filter(id=thread_id).select_related('owner').prefetch_related(*prefetches)
return qs
結果
送るqueryの数も少なくなってchild objectの量が多くなってもqueryの数は変わらない。
特に、prefetch_relatedを使うとき、django.db.models
からPrefetch
をimportして使えばもっと効率良くqueryの改善ができる。
django orm order_byを条件付きorder_byへ
model structure
Thread > Comment > Reply
ThreadをソートするときThreadに属しているCommentのcreated_atを元にソートしようと思っていたが
したのコードだとThreadにCommentを何も追加してなかったらnull
を返してしまいorder_byが動作しない事に気づいた。
query_filter = None
query_order = ['-updated']
query_annotate = {
'updated': Max('comments__updated_at'),
'count_likes': Count('likes', distinct=True),
'count_comments': Count('comments', distinct=True)
}
if order == 'latest':
pass
else:
if order == 'daily':
query_filter = Q(updated__range=[now - timedelta(days=1), now])
elif order == 'weekly':
query_filter = Q(updated__range=[now - timedelta(days=7), now])
elif order == 'alltime':
pass
query_order.insert(0, '-count_likes')
if query_filter:
query = qs.annotate(**query_annotate).filter(query_filter).distinct().order_by(*query_order)
else:
query = qs.annotate(**query_annotate).distinct().order_by(*query_order)
return query
ここでquery_annotate変数を下記のように書き換えてみた。
query_annotate = {
'updated': Case(
When(Exists(Comment.objects.filter(thread_id=OuterRef('id'))), then=Max('comments__updated_at')),
default=F('created_at'),
output_field=DateTimeField(),
),
'count_likes': Count('likes', distinct=True),
'count_comments': Count('comments', distinct=True)
}
query_annotate
のupdated
fieldを上のように書き換えてthreadにcommentsがあるときは一番最近追加したcommentのupdated_atを返して、commentsがなかったらthread自身のcreated_atを返すことにしてcommentがなくてもorder_byが動作するように修正した。
上記のupdatedに書いたコードをquery文にしたら次のようなqueryになる。
CASE
WHEN EXISTS
(SELECT (1) AS "a"
FROM "thlog_comment" U0
WHERE U0."thread_id" = "thlog_thread"."id"
LIMIT 1) THEN "thlog_thread"."created_at"
ELSE MAX("thlog_comment"."updated_at")
END AS "updated",
uty