Django:1.10
Djangoでクエリを生成する時、よく考えて書かないと大量にクエリが発行されてしまいます。
クエリのログ出力
まずはクエリをサーバのコンソールに出力するため、manage.pyにログの設定を書く。
LOGGING = {
'version': 1,
'formatters': {
'all': {
'format': '%(message)s'
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'all',
},
},
'loggers': {
'django': {
'handlers': ['console'],
'level': 'DEBUG',
},
},
}
クエリの確認
前回使った、選択した著者の出版した本一覧を取得するクエリを実行してみます。
DjangoでIN演算子を使ったクエリを生成する方法
http://qiita.com/nakkun/items/86a94e65fe6785325f54
books = Book.objects.filter(author_id__in = form.cleaned_data['author'])
(0.002) SELECT "books_book"."id", "books_book"."name", "books_book"."author_id" FROM "books_book" WHERE "books_book"."author_id" IN (1, 3); args=(1, 3)
想像通りのSQLが実行されていました。
以下のように、「author__id__in」を使っても同じクエリになりました。
(「_」の数が違う)
books = Book.objects.filter(author__id__in = form.cleaned_data['author'])
「author__id__in」の場合は、joinしてくれるのかなと思っていましたが、そうなっていませんでした。
クエリ大量発生!?
ここまでは特に問題ないのですが、その後に著者名を表示しようとした場合、以下のような実行結果になってしまいます。
books = Book.objects.filter(author_id__in = form.cleaned_data['author'])
[print(book.author.name) for book in books]
(0.003) SELECT "books_book"."id", "books_book"."name", "books_book"."author_id" FROM "books_book" WHERE "books_book"."author_id" IN (1, 3); args=(1, 3)
(0.001) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" = 1; args=(1,)
森 博嗣
(0.000) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" = 3; args=(3,)
宮城谷 昌光
(0.000) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" = 3; args=(3,)
宮城谷 昌光
(0.001) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" = 3; args=(3,)
宮城谷 昌光
(0.000) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" = 3; args=(3,)
宮城谷 昌光
かなり無駄なクエリを発行してしまっています。
なので、次のようなjoinしたクエリを発行してくれるソースを書く必要があります。
books = Book.objects.select_related().filter(author_id__in = form.cleaned_data['author'])
[print(book.author.name) for book in books]
(0.004) SELECT "books_book"."id", "books_book"."name", "books_book"."author_id", "books_author"."id", "books_author"."name" FROM "books_book" INNER JOIN "books_author" ON ("books_book"."author_id" = "books_author"."id") WHERE "books_book"."author_id" IN (1, 3); args=(1, 3)
森 博嗣
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光
これでSQLの発行が1回で済みます。
知らず知らずのうちに大量のSQLを投げてしまわないように、慣れないうちはSQLを確認しながら開発した方がいいかもしれません。
補足
他の方法を3つほど。
① prefetch_relatedを使う方法
books = Book.objects.filter(author_id__in = form.cleaned_data['author']).prefetch_related('author')
[print(book.author.name) for book in books]
(0.001) SELECT "books_book"."id", "books_book"."name", "books_book"."author_id" FROM "books_book" WHERE "books_book"."author_id" IN (1, 3); args=(1, 3)
(0.001) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" IN (1, 3); args=(1, 3)
森 博嗣
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光
② Authorモデルを起点にする
authors = Author.objects.filter(id__in = form.cleaned_data['author'])
[[print(book.atuhor.name) for book in author.book_set.all()] for author in authors]
(0.002) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" IN (1, 3); args=(1, 3)
(0.001) SELECT "books_book"."id", "books_book"."name", "books_book"."author_id" FROM "books_book" WHERE "books_book"."author_id" = 1; args=(1,)
森 博嗣
(0.000) SELECT "books_book"."id", "books_book"."name", "books_book"."author_id" FROM "books_book" WHERE "books_book"."author_id" = 3; args=(3,)
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光
③ Authorモデルを起点にprefetch_relatedを使う
authors = Author.objects.filter(id__in = form.cleaned_data['author']).prefetch_related("book_set")
[[print(book.atuhor.name) for book in author.book_set.all()] for author in authors]
(0.002) SELECT "books_author"."id", "books_author"."name" FROM "books_author" WHERE "books_author"."id" IN (1, 3); args=(1, 3)
(0.002) SELECT "books_book"."id", "books_book"."name", "books_book"."author_id" FROM "books_book" WHERE "books_book"."author_id" IN (1, 3); args=(1, 3)
森 博嗣
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光
宮城谷 昌光