DjangoのQuerySet API QuerySetを返さないものたち編

QuerySetを返さないQuerySet API

QuerySet API第二回目です。

get(**kwargs)

与えられたパラメータと合致するオブジェクトを1つ取得する。もし1つ以上のオブジェクトがパラメータにヒットしたらMultipleObjectsReturnedエラー が返ってくるので注意。pkとかユニークな値を使ってしか取得しちゃいかん感じ。またもし1つもヒットするオブジェクトがないとDoenNotExistエラーが返ってくる。本当にある1つのデータが欲しい時のみ使う。

create(**kwargs)

新しいオブジェクトを作るときに使うメソッド。

entry = Entry.objects.create(title="タイトル", body="本文")

force_inset()っていうメソッドもあって、これは必ず新しいオブジェクトを作るんだけど、create()でだいたい作れるから普通はcreate()で作って問題ないとのこと。たとえば、もしpkを手動で設定している場合にpkを設定し忘れたり、pkが一意じゃないとIntegrityErrorが返ってくる。

get_or_create(defaults=None, **kwargs)

キーワード引数の条件でオブジェクトを探して、存在しない場合は新しいオブジェクトを作成する。返り値は(Object, created)のタプル。createdは新たにオブジェクトが作成された場合はTrueが返ってきます。本来、get()と例外処理を使って書くところをこのメソッド1つで書ける場合がある。便利。ただし、このメソッドも複数のオブジェクトが見つかるとMultipleObjectsReturnedエラーが発生するので注意。

update_or_create(defaults=None, **kwargs)

指定されたオブジェクトのデータをアップデートする。もし指定されたオブジェクトが存在しなければ作成する。get_or_create()と同じく(Object, created)を返す。辞書型をdefaultsというキーワード引数で渡して、ヒットしたオブジェクトはそのキーワード引数の値にアップデートされる。

obj, created = Person.objects.update_or_create(
    first_name='John', last_name='Lennon',
    defaults={'first_name': 'Bob'},
)

keyにフィールド名、valueに実際のデータを代入する。

bulk_create(objs, batch_size=None)

オブジェクトのリストを一度のクエリでDBに保存できる。

>>> Entry.objects.bulk_create([
     Entry(headline='This is a test'),
     Entry(headline='This is only a test'),
])

効率が良くて便利ではあるが、注意点がいくつかある。なにを隠そうこのAPI調べてみようと僕が思ったきっかけは、このメソッドにハマったから。。。。

このモデルはsave()を呼び出さない。pre_savepost_saveというシグナルも送らない。

もしpkがAutoFieldに設定されてるならpkをセットしない、またpkの取得もできない。

ここを知らずに僕はbulk_create()したあとにpkを取得しようとして、「あれ取得できない、なんでやろ」と困り果ててました。またM2Mの関係だと動作しないという注意点もあります。ほかにも数点注意すべきことがあるようです。

キーワード引数のbatch_sizeは一度のクエリで何個までオブジェクトを生成するかを指定できます。

count()

オブジェクト数をintegerで返します。実際はSELECT COUNT(*)というSQL文が実行されていて、処理の重さが違うのですべてのオブジェクトを取得してlen()するのはよしましょう。

in_bulk(id_list=None, field_name='pk')

リストにフィールドの値を代入して、キーワード引数filed_nameでフィールドを指定すると、与えたリストの値に該当するオブジェクトを返します。

Entry.objects.in_bulk([1])
{1: <Entyr: Hoge>}
#返り値は辞書型

iterator(chunk_size=2000)

QuerySetは取得結果をキャッシュしているので、同じ処理を繰り返しても追加のクエリ発行されません。一方で、iterator()はDBからキャッシュを行わず一つ一つのオブジェクトをDBから直接取ってきます。

latest(*fields)

最新のオブジェクトを取得します。複数のフィールドを指定することで条件をより高度なものにできます。

Entry.objects.latest('published_at', 'updated_at')
# 最新の日付が複数あったとき、更新が新しい方を取得する

またMetaget_latest_byを設定することで引数を設定しなくてもMetaで設定した条件で取得できる。

earliest(*fields)

latest()とは逆に最古のデータを取得できる。

first()

クエリセットの一番最初のオブジェクトを取得する。オブジェクトが存在しないばあいはNoneを返す。クエリセットに[0]というインデックスを付与して最初のオブジェクトを取得しようとするとオブジェクトが無い場合に備えて例外処理を追加しなければいけないが、first()なら存在しないばあいはNoneを返すので処理が簡潔に済む。

last()

first()の逆で一番最後のオブジェクトを取得する。

aggregate(*args, **kwargs)

QuerySetのデータを集計したものを辞書型で返す。

from django.db.models import Count
 q = Blog.objects.aggregate(Count('entry'))
{'entry__count': 16}

exists()

指定したオブジェクトが存在する場合はTrue、存在しない場合Falseを返す。

update(**kwargs)

指定されたクエリセットの指定したフィールドを更新する。

Entry.objects.filter(title='hoge').update(title='fuga')

delete()

指定されたクエリセットを削除。

Entry.objects.filter(author="kai").delete()
# kaiの投稿をすべて削除

explain(format=None, **options)

Django2.1で追加された新しめのメソッド。QuerySetの実行プランを文字列で返します。手元のDjangoが2.1未満なので実際に実行できず。。。ドキュメントから実行例をひろってきました。

print(Blog.objects.filter(title='My Blog').explain())
Seq Scan on blog  (cost=0.00..35.50 rows=10 width=12)
  Filter: (title = 'My Blog'::bpchar)

キーワード引数のformatはTEXT, JSON, YAML, XMLのどれか1つを受けとり指定されたフォーマットで出力します。