はじめに
最近、QuerySet APIについてよく調べていました。今日はQuerySet APのメソッドのなかでもドキュメントの文量が多めなselect_related()
をすこし詳しく学習します。なにかと利用する機会がありそう。(あと923がselect_related()
は奥が深いぞ的なことを言っていたので)
ざっくりした理解
select_related(*fields)
クエリを実行したときに、指定された外部キーのオブジェクトも一緒にとってくる。というやつ。それによって、DBを叩く回数を節約できる。
# DBを叩く e = Entry.objects.get(id=5) # またDBを叩いてる b = e.blog
こういうコードを以下の様に書ける。
# DBを叩く e = Entry.objects.select_related('blog').get(id=5) # 予め取得したオブジェクトを参照しているのでDBを叩いてない b = e.blog
もうちょっと詳しく
迂闊な経験
一度や二度のアクセスならまだしも、たくさんのオブジェクトをfor文で回して外部キーを参照するとすごい沢山の回数DBを叩くことになる。ぼくこれは経験があります。もうあんな目に遭うのはいやです。そのためにももう少しselect_related()
について詳しく学んで今後に備えます。
外部キーの外部キーを取ってくるパターン。
ここに街と著者と記事のモデルがあります。
from django.db import models class City(models.Model): # ... pass class Author(models.Model): # ... hometown = models.ForeignKey( City, on_delete=models.SET_NULL, blank=True, null=True, ) class Entry(models.Model): # ... author = models.ForeignKey(Author, on_delete=models.CASCADE)
そして記事の著者の故郷を知りたいとします。まずはselect_related
を使用しないパターン。
b = Entry.objects.get(id=4) # DBを叩く p = b.author # DBを叩く c = p.hometown # DBを叩く
愚直に行えばこのように計3回叩くことになることになります。続いてselect_related
を使うパターン。
# 一度のクエリでauthorとhometownテーブルからもオブジェクトを取得する b = Entry.objects.select_related('author__hometown').get(id=4) p = b.author # 取得済みのオブジェクトを参照 c = p.hometown #取得済みのオブジェクトを参照
DBを叩きにいく回数が1回で済みました。やったね!
リレーション先のリストをクリアする
select_related
で取得したリレーション先のオブジェクトをクエリセットからクリアしたい時がいつか来るかもしれません。これをするにはキーワード引数にNoneを与えて実行します。
without_relations = queryset.select_related(None)
さよならリレーション。
複数のselect_related()
をチェーンするパターン
これは
select_related('foo').select_related('bar').
こう書けます
select_related('foo', 'bar')
頭の片隅に入れておきます。
リレーション先のオブジェクトが存在しないパターン
どうなるか見てみましょう。
# ブログ記事の作者を確認 entry.author # 作者はゴリラでした <Author: ゴリラ> entry.author.pk 1 # ゴリラを削除 Author.objects.get(pk=1).delete() entry = Entry.objects.select_related('author').first() entry <Entry: ゴリラです。ブログ始めました。> entry.author == None True
ゴリラオブジェクトを削除しても平気な顔してQuerySetを用意してきました。しれっとNone
が入ってます。エラーは帰ってきません。これを忘れるといつか痛い目に遭うと思うので覚えておきます。
引数を指定しないパターン
引数を指定しないとnull=False
の外部キーのみが取得対象になる。基本は引数を与えるべし。明示的にいこう。
リレーション先のデータを取得したいパターン
とりあえずリレーション先のデータがほしいときは__
(アンダースコア2つ)を使った表現を使う。ゴリラオブジェクトのname
とdescription
を取得してみます。
entry = Entry.objects.filter(pk=1).select_related( 'author' ).values('author__name','author__description') <AuthorQuerySet [{'author__name': 'ゴリラ', 'author__description': 'ワイルドだぜぇ。'}]>
取得できました。これも意外と使い所があるかも。
おわりに
これでもうselect_related
を恐れること無く使えます。
参考リンク
QuerySet API reference | Django documentation | Django
Django ORM の select_related, prefetch_related の挙動を詳しく調べてみた - akiyoko blog