効率的にQueryを取り出す方法
効率的にQueryを取り出す方法を紹介します。
SQLを大量に実行すると、表示に時間がかかってしまいます。
そこで、select_related
とprefetch_related
を使用することで、SQLを実行する回数を減らすことができます。
設定
SQLのログを出すことによって、画面を表示したときにどのSQLコマンドが実行されているかを確認することができます。
LOGGING
settings.py
LOGGING = {
'version': 1,
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
}
},
'loggers': {
'django.db.backends': {
'level': 'DEBUG',
'handlers': ['console'],
},
}
}
models
モデルは、BlogとAuthorを作成します。
models.py
from django.db import models
# Create your models here.
class Author(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Blog(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
content = models.CharField(max_length=100)
def __str__(self):
return self.content
views
ブログモデルのすべてのデータをテンプレートで表示します。
views.py
from django.views.generic import View
from django.shortcuts import render
from .models import Blog, Author
class IndexView(View):
def get(self, request, *args, **kwargs):
blog_data = Blog.objects.all()
return render(request, 'app/index.html', {
'blog_data': blog_data,
})
index
index.html
{% for blog in blog_data %}
<p>{{ blog.author.name }}</p>
<p>{{ blog.content }}</p>
<hr>
{% endfor %}
BlogモデルからAuthorモデルを読み込むとこれだけのSQLが実行されます。
(0.002) SELECT "app_blog"."id", "app_blog"."author_id", "app_blog"."content" FROM "app_blog"; args=()
(0.001) SELECT "app_author"."id", "app_author"."name" FROM "app_author" WHERE "app_author"."id" = 1; args=(1,)
(0.001) SELECT "app_author"."id", "app_author"."name" FROM "app_author" WHERE "app_author"."id" = 2; args=(2,)
(0.001) SELECT "app_author"."id", "app_author"."name" FROM "app_author" WHERE "app_author"."id" = 2; args=(2,)
select_related
select_related
を使うことによって、SQLの実行回数を減らすことができます。
1対多の関係のデータがあり、多の一覧を表示する時に、同時に1の情報も表示したいときに使用します。
この場合は、ブログの一覧(多)を表示する時に、著者(1)を表示したい時です。
views
views.py
class IndexView(View):
def get(self, request, *args, **kwargs):
blog_data = Blog.objects.select_related('author')
return render(request, 'app/index.html', {
'blog_data': blog_data,
})
SQL
SQL実行回数は1回のみとなりました。
(0.002) SELECT "app_blog"."id", "app_blog"."author_id", "app_blog"."content", "app_author"."id", "app_author"."name" FROM "app_blog" INNER JOIN "app_author" ON ("app_blog"."author_id" = "app_author"."id"); args=()
画面
prefetch_related
prefetch_related
を使うことによって、SQLの実行回数を減らすことができます。
1対多、多対多の関係の時に、相手方の多の情報を一緒に表示したい時に使用します。
この場合は、著者(1)を表示する時に、ブログの一覧(多)を表示したい時です。
views
views.py
class IndexView(View):
def get(self, request, *args, **kwargs):
author_data = Author.objects.prefetch_related('blog_set')
return render(request, 'app/index.html', {
'author_data': author_data,
})
index
index.html
{% for author in author_data %}
<p>{{ author.name }}</p>
{% for blog in author.blog_set.all %}
<p>{{ blog.content }}</p>
{% endfor %}
<hr>
{% endfor %}
SQL
SQL実行回数は2回のみとなります。
(0.003) SELECT "app_author"."id", "app_author"."name" FROM "app_author"; args=()
(0.001) SELECT "app_blog"."id", "app_blog"."author_id", "app_blog"."content" FROM "app_blog" WHERE "app_blog"."author_id" IN (1, 2); args=(1, 2)
画面
モデルでデータを取得
AuthorモデルからBlogモデルを取得することもできます。
models
models.py
from django.db import models
# Create your models here.
class Author(models.Model):
name = models.CharField(max_length=100)
def get_blog(self):
blogs = []
for blog in self.blog_set.order_by("id"):
blogs.append(blog)
return blogs
def __str__(self):
return self.name
class Blog(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
content = models.CharField(max_length=100)
def __str__(self):
return self.content
views
Authorモデルをテンプレートに渡します。
views.py
from django.views.generic import View
from django.shortcuts import render
from .models import Author
class IndexView(View):
def get(self, request, *args, **kwargs):
author_data = Author.objects.all()
return render(request, 'app/index.html', {
'author_data': author_data,
})
index
Authorモデルのget_blog関数をコールすると、Authorに紐づいているBlogモデルを取得することができます。
index.html
{% for author in author_data %}
<p>{{ author.name }}</p>
{% for blog in author.get_blog %}
<p>{{ blog.content }}</p>
{% endfor %}
<hr>
{% endfor %}
画面
まとめ
select_related
とprefetch_related
を使用して、効率よくデータを収集しましょう。
モデルに関数を追加すると、効率よくデータを取得できる場合もあります。
Discussion