奥深いQuerySet APIの世界の入り口
CBVをうまく使えばViewsを簡潔に書けることがわかった。ほんとCBVはパワフル。「CBV最高だぜ〜〜ひゃっは〜〜」と調子に乗ってるとき、Viewsのなかでもう1つパワフルに暴れまわってくれる相棒を見つけた。QuerySet APIくんです。
例によって参考元はDjangoのドキュメント。
QuerySet
APIをチェック
最近、Viewsのなかでは息をするようにデータベースをいじっていたので、QuerySet
のAPIをざっと見ておけばいざというとき頭の中のインデックスが反応してくれるんじゃないかと期待しています。Djangoのドキュメントにならってコード例はブログっぽい感じにしてみた。とりあえず、QuerySet
を返すAPIだけ。
filter(**kwargs)
与えられたパラメータにマッチしたオブジェクトを返してくれる。
Entriy.objects.filter(tag="Django")
Djangoタグが付いた記事だけひっぱってくる。
exclued(**kwargs)
与えられたパラメータにマッチしなかったオブジェクトを返してくれる。
Entriy.objects.filter(tag="Django")
Djangoタグがついてないすべての記事をひっぱってくる。はぶりだ。
annotate(*args, **kwargs)
ちょっとトリッキーなやつ。コードをまず見てみよう。
from django.db.models import Count e = Entry.objects.annotate(Count('tags')) e[0].tags e[0].tags__count 2
このオブジェクトのtags
にtags__count
なんてメソッドは本来無い。けど、予めCount
を読み込んでいるので利用可能になっている。Aggregation functions
という関数群があって、Count
もその中の1つ。これを使いこなすにはAggregation functions
も学ぶ必要があるなぁ〜〜。でもうまく使えればコードを綺麗に保てるね!
order_by(*fields)
デフォルトだとモデルのmeta
で順番を決められるらしいけど、order_by
は呼び出すタイミングでその順番を変えることができる。
entries = Entry.objects.order_by('published_at')
昇順でソートして記事を取得。ちなみにorder_by('?')
はランダムらしい。おもろい。でも、処理が重くなるから注意とのこと。
reverse()
クエリセットの要素の順番を逆にする。
……なんか手元でreverse()
できなかった(´;ω;`)
Djangoのドキュメントから引っ張ってきました。活用すればこんなふうに最新の5個のクエリセットを取ってこられるらしい。
my_queryset.reverse()[:5]
なんで動かないの(´;ω;`)
distinct(*fields)
重複データを取り除いてくれるやつ。ふつう意図して重複を許さない限り重複があることじたいよくないと思う。あんまり使いみちが……と思ったけど、今日バリバリ使った。なんか2つのクエリセットを1つにしようとしてた記憶がある(遠い目)。
Entry.objects.order_by('title').distinct('title')
重複したタイトルは抹殺されました。
values(*fields, **expressions)
辞書型のクエリセットを返します。モデルインスタンスとしてよりイテレーブルなデータとして扱いたい時にどうぞらしいです。
Entry.objects.values().first() {'id': 1, 'author_id': 1, 'title': '猫の手借りたい丸の冒険その一', 'category_id': 1, 'contents': 'その旅がいつ始まったかは、もう覚えていない。', 'published_at': datetime.date(2018, 11, 2), 'valid': True}
取得したいフィールドを指定することも可能。
Entry.objects.values('id', 'author_id').first() {'id': 1, 'author_id': 1}
しかも引数に**expressions
があるのでannotate()
を併用することができるらしい。頭痛くなってきた。それと外部キーは参照先のidが代入されるらしい。ほかにもいろいろ注意書きがあったからここにはいつか戻ってくる気がする。
values_list()
今度はtupleでクエリセットを返してくれるやつです。
Entry.objects.values_list().first() (1, 1, '猫の手借りたい丸の冒険その一', 1, 'その旅がいつ始まったかは、もう覚えていない。', datetime.date(2018, 11, 2), True)
フィールドを指定するとそのフィールドだけをtupleでかっぱらってきてくれる。記事の投稿日を指定してみた。
[(datetime.date(2018, 11, 2),), (datetime.date(2018, 11, 2),), (datetime.date(2018, 11, 2),)]
「え、tupleいらないんだけど」ってときは.values_list('published_at', flat=True)
という感じでflat
オプションを使う。
[datetime.date(2018, 11, 2), datetime.date(2018, 11, 2), datetime.date(2018, 11, 2)]
計画通り(にちゃあ
dates(field, kind, order='ASC')
日付を扱います。以下のようなデータがDBに入ってるとします。
datetime.date(2018, 1, 1) datetime.date(2018, 2, 1) datetime.date(2018, 2, 2) datetime.date(2019, 2, 2)
Entry.objects.dates('published_at, 'year')
とすれば重複しないyear
が取り出されます。つまりdatetime.date(2018, 1, 1)
とdatetime.date(2019, 2, 2)
を取得されます。
Entry.objects.dates('published_at, 'month')
とすると今度は以下の2つが取得されます。
datetime.date(2018, 1, 1) datetime.date(2018, 2, 1)
こんな要領で使えるやつらしいです。順番は基本昇順ですが、引数にDESC
を加えれば降順にもできるらしい。
datetimes(field_name, kind, order="ASC", tzinfo=None)
datetime版です。tzinfo
引数でタイムゾーンを設定できます。
none()
何のオブジェクトも返さないクエリセットを生成するらしい。哲学かな。
使いみちがいまいち見えなかったのでGitHubで実態を調べてみた。objects.none() filename:views.py
で5Kくらいヒットした。だらっと見かけた実用例。
- ログイン認証で失敗したとき、
return Hoge.objects.none()
results = Hoge.objects.none()
で最初に初期化- 例外処理で
return Hoge.objects.none()
ここらへんが多かった。なるほど。
all()
全員集合!(すべてのオブジェクトを取得)
union(*other_qs, all=False)
クエリセットを合体する。重複したデータは覗かれて、一意なオブジェクトのクエリセットができる。all=True
を引数に設定すると重複もすべて合体する。
intersection(*other_qs)
複数のクエリセットが保持している同じオブジェクトだけを抽出する。
qs_a.intersection(qs_b)
difference(*other_qs)
qs_a.intersection(qs_b)
複数のクエリでqs_a
とqs_b
の差分のオブジェクトを抽出する。
select_related(*fields)
外部キーのオブジェクトを参照することができる。
# DBを叩く e = Entry.objects.get(id=5) # またDBを叩く b = e.blog
こういう処理をいっぺんに済ませる。
e = Entry.objects.select_related('blog').get(id=5) # e.blogは予め呼び出されているのでDBにアクセスしないでいい b = e.blog
実践的なテクニックだ。
prefetch_related(*lookups)
これはselect_related
に少し似てるメソッドみたい。どちらもDBに関連するオブジェクトを取りに行くアクセスを減らすためにある。
select_related
はSQLのSELECT
ステートメントで、関連するオブジェクトを1つのクエリのなかで取得する。しかし、M2Mで多くのオブジェクトと関係を持つものをselect_related
で取得するのは避けたほうがいいみたい。select_related
は1対1の関係のオブジェクトにのみ使うものだそうだ。
一方prefetch_related
はそれぞれのオブジェクトを個別に取得してそのあとにjoining
しているらしい。この処理の違いからprefetch_related()
はM2Mの外部キーのオブジェクトにも対応している。使い分けである。
extra(select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
引数の種類がおおい。よくみるとSQLのコマンドばかりだ。これはDjangoのクエリ操作だけじゃ解読困難に陥るようなコードをSQLコマンドで書くもの。以下の2つは等価。
qs.annotate(val=RawSQL("select col from sometable where othercol = %s", (someparam,)))
qs.extra( select={'val': "select col from sometable where othercol = %s"}, select_params=(someparam,), )
defer(*fields)
不要なフィールドはとってこないで!とお願いできるメソッド。たとえばでっかいデータを保持するcontent
フィールドがあったとして、10,000件のオブジェクトをとってこようとすると処理が遅くなる。ので、defer()
でcontent
はいらないよ!と伝えようということ。
しかし、取得したオブジェクトのcontent
フィールドにアクセスするとそのタイミングでDBにアクセスしてデータを取得してきてくれるらしい。本当にとってきてないのかぁ〜〜シュレディンガーのにゃんにゃんみたいだ。
only(*fields)
逆にほとんどのフィールドをとってこなくていいときはonly()
で必要なものだけとってくる。
using(alias)
複数のDBを使っている時、使用するDBを指定する。
Entry.objects.using('backup')
select_for_update(nowait=False, skip_locked=False)
ちょっとこれだけよくわからなかった。row
をロックしてほかの処理をうんちゃらかんちゃら。SQLの基礎知識が/(^o^)\
QuerySet API reference | Django documentation | Django
raw(raw_query, params=None, translations=None)
SQLクエリを実行できる。返り値の型はdjango.db.models.query.RawQuerySet
。
おわりに
知らない権利は無いのと一緒のように知らないメソッドも無いのと一緒だと痛感した1日だったので、鉄は熱いうちに打てという気持ちでざっと見てみました。