🦉

効率的にQueryを取り出す方法

2021/10/19に公開

効率的にQueryを取り出す方法を紹介します。

SQLを大量に実行すると、表示に時間がかかってしまいます。

そこで、select_relatedprefetch_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を使うことによって、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を使うことによって、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_relatedprefetch_relatedを使用して、効率よくデータを収集しましょう。

モデルに関数を追加すると、効率よくデータを取得できる場合もあります。

Discussion