Djangoで生成されたクエリを確認したら、大量に発行されていた

Django:1.10

Djangoでクエリを生成する時、よく考えて書かないと大量にクエリが発行されてしまいます。

# クエリのログ出力

まずはクエリをサーバのコンソールに出力するため、manage.pyにログの設定を書く。

```python: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

```python:ソース
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」を使っても同じクエリになりました。
(「_」の数が違う)

```python:ソース
books = Book.objects.filter(author__id__in = form.cleaned_data['author'])
```

「author_\_id_\_in」の場合は、joinしてくれるのかなと思っていましたが、そうなっていませんでした。

# クエリ大量発生！？

ここまでは特に問題ないのですが、その後に著者名を表示しようとした場合、以下のような実行結果になってしまいます。

```python:ソース
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したクエリを発行してくれるソースを書く必要があります。

```python:ソース
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を使う方法

```python:ソース
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モデルを起点にする

```python:ソース
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を使う

```python:ソース
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)
森 博嗣
宮城谷　昌光
宮城谷　昌光
宮城谷　昌光
宮城谷　昌光
```