select_related と prefetch_related で発行されるクエリを確認してみる

select_related と prefetch_related で発行されるクエリを確認してみる

とりあえず後日書き足せれば...

箇条書きで...

【モデル】


from django.db import models


class Writer(models.Model):
    name = models.CharField(max_length=128)


class Publisher(models.Model):
    name = models.CharField(max_length=128)


class Novel(models.Model):
    title = models.CharField(max_length=512)
    writer = models.ForeignKey(Writer, on_delete=models.CASCADE)
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)


class Comic(models.Model):
    title = models.CharField(max_length=512)
    writer = models.ForeignKey(Writer, on_delete=models.CASCADE)
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE)

【データ】


myapp_writer
| id  | name    |
| --- | ------- |
| 1   | john    |
| 2   | robert  |
| 3   | michael |
| 4   | albert  |
| 5   | ken     |

myapp_publisher
| id  | name          |
| --- | ------------- |
| 1   | academic.inc  |
| 2   | blackwell.inc |
| 3   | macmillan.inc |
| 4   | oxford.inc    |
| 5   | thomason.inc  |

myapp_novel
| id  | title      | publisher_id | writer_id |
| --- | ---------- | ------------ | --------- |
| 1   | happy_life | 1            | 5         |
| 2   | stay_here  | 4            | 4         |
| 3   | great_day  | 4            | 3         |
| 4   | the_world  | 5            | 2         |
| 5   | my_life    | 5            | 1         |

myapp_comic
| id  | title           | publisher_id | writer_id |
| --- | --------------- | ------------ | --------- |
| 1   | captain_america | 1            | 5         |
| 2   | iron_man        | 2            | 5         |
| 3   | spider_man      | 2            | 5         |
| 4   | hulk            | 3            | 2         |
| 5   | dead_pool       | 3            | 1         |

【ビュー】


from django.shortcuts import render
from django.db.models import Prefetch
from .models import Writer
from .models import Publisher
from .models import Novel
from .models import Comic

def index(request):
    novel = Novel.objects.select_related('writer').get(id=1)
    print(f'{novel.title} の著者は {novel.writer.name} です。')
    # 結果
    # happy_life の著者は ken です。
    """
    SELECT 
        `myapp_novel`.`id`, 
        `myapp_novel`.`title`, 
        `myapp_novel`.`writer_id`, 
        `myapp_novel`.`publisher_id`, 
        `myapp_writer`.`id`, 
        `myapp_writer`.`name` 
    FROM 
        `myapp_novel` 
    INNER JOIN 
        `myapp_writer` 
    ON 
        (
            `myapp_novel`.`writer_id` = `myapp_writer`.`id`
        ) 
    WHERE 
        `myapp_novel`.`id` = 1 LIMIT 21;
    """

    novels = Novel.objects.select_related('writer').filter(title__contains='life')
    for novel in novels:
        print(f'タイトルに「life」がつくこの本 {novel.title} は {novel.writer.name} によって書かれました。')
        # 結果
        # タイトルに「life」がつくこの本 happy_life は ken によって書かれました。
        # タイトルに「life」がつくこの本 my_life は john によって書かれました。
    """
    SELECT 
        `myapp_novel`.`id`, 
        `myapp_novel`.`title`, 
        `myapp_novel`.`writer_id`, 
        `myapp_novel`.`publisher_id`, 
        `myapp_writer`.`id`, 
        `myapp_writer`.`name` 
    FROM 
        `myapp_novel` 
    INNER JOIN 
        `myapp_writer` 
    ON 
        (
            `myapp_novel`.`writer_id` = `myapp_writer`.`id`
        ) 
    WHERE 
        `myapp_novel`.`title` LIKE BINARY '%life%' 
    LIMIT 21;
    """
    
    novels = Novel.objects.select_related('writer').all()
    for novel in novels:
        print(f'{novel.title} は著者が {novel.writer.name} です。')
        # 結果
        # happy_life は著者が ken です。
        # stay_here は著者が albert です。
        # great_day は著者が michael です。
        # the_world は著者が robert です。
        # my_life は著者が john です。
    """
    SELECT 
        `myapp_novel`.`id`, 
        `myapp_novel`.`title`, 
        `myapp_novel`.`writer_id`, 
        `myapp_novel`.`publisher_id`, 
        `myapp_writer`.`id`, 
        `myapp_writer`.`name`
    FROM 
        `myapp_novel` 
    INNER JOIN 
        `myapp_writer` 
    ON 
        (
            `myapp_novel`.`writer_id` = `myapp_writer`.`id`
        );
    """

    novels = Novel.objects.select_related('writer', 'publisher').all()
    for novel in novels:
        print(f'{novel.title} は著者が {novel.writer.name} で出版社は {novel.publisher.name} です。')
        # 結果
        # happy_life は著者が ken で出版社は academic.inc です。
        # stay_here は著者が albert で出版社は oxford.inc です。
        # great_day は著者が michael で出版社は oxford.inc です。
        # the_world は著者が robert で出版社は thomson.inc です。
        # my_life は著者が john で出版社は thomson.inc です。
    """
    SELECT 
        `myapp_novel`.`id`, 
        `myapp_novel`.`title`, 
        `myapp_novel`.`writer_id`, 
        `myapp_novel`.`publisher_id`, 
        `myapp_writer`.`id`, 
        `myapp_writer`.`name`, 
        `myapp_publisher`.`id`, 
        `myapp_publisher`.`name` 
    FROM 
        `myapp_novel` 
    INNER JOIN 
        `myapp_writer` 
    ON 
        (
            `myapp_novel`.`writer_id` = `myapp_writer`.`id`
        ) 
    INNER JOIN 
        `myapp_publisher` 
    ON 
        (
            `myapp_novel`.`publisher_id` = `myapp_publisher`.`id`
        ) 
    LIMIT 21;
    """

    writers = Writer.objects.prefetch_related('comic_set').all()
    for writer in writers:
        print(f'{writer.name} はコミックを {writer.comic_set.count()} 冊書きました。')
        # 結果
        # john はコミックを 1 冊書きました。
        # robert はコミックを 1 冊書きました。
        # michael はコミックを 0 冊書きました。
        # albert はコミックを 0 冊書きました。
        # ken はコミックを 3 冊書きました。
    """
    SELECT 
        `myapp_writer`.`id`, 
        `myapp_writer`.`name` 
    FROM 
        `myapp_writer` 
    LIMIT 21;

    SELECT 
        `myapp_comic`.`id`, 
        `myapp_comic`.`title`, 
        `myapp_comic`.`writer_id`, 
        `myapp_comic`.`publisher_id` 
    FROM 
        `myapp_comic` 
    WHERE 
        `myapp_comic`.`writer_id` 
    IN (1, 2, 3, 4, 5);
    """
    
    writers = Writer.objects.prefetch_related('comic_set', 'novel_set').all()
    for writer in writers:
        print(f'{writer.name} はコミックを {writer.comic_set.count()} 冊、小説を {writer.comic_set.count()} 冊書きました。')
        # 結果
        # john はコミックを 1 冊、小説を 1 冊書きました。
        # robert はコミックを 1 冊、小説を 1 冊書きました。
        # michael はコミックを 0 冊、小説を 0 冊書きました。
        # albert はコミックを 0 冊、小説を 0 冊書きました。
        # ken はコミックを 3 冊、小説を 3 冊書きました。
  
    """
    SELECT 
        `myapp_writer`.`id`, 
        `myapp_writer`.`name` 
    FROM 
        `myapp_writer` 
    LIMIT 21;
    
    SELECT 
        `myapp_comic`.`id`, 
        `myapp_comic`.`title`, 
        `myapp_comic`.`writer_id`, 
        `myapp_comic`.`publisher_id` 
    FROM 
        `myapp_comic` 
    WHERE 
        `myapp_comic`.`writer_id` 
    IN (1, 2, 3, 4, 5);

    SELECT 
        `myapp_novel`.`id`, 
        `myapp_novel`.`title`, 
        `myapp_novel`.`writer_id`, 
        `myapp_novel`.`publisher_id` 
    FROM 
        `myapp_novel` 
    WHERE 
        `myapp_novel`.`writer_id` 
    IN (1, 2, 3, 4, 5);
    """

    writers = Writer.objects.prefetch_related(
        Prefetch(
            'comic_set', queryset=Comic.objects.select_related('publisher')
        )
    ).all()
    for writer in writers:
        [print(f'{writer.name} は {comic.title} の出版を {comic.publisher.name} に依頼しました。') for comic in writer.comic_set.all()]
        # 結果
        # john は dead_pool の出版を macmillan.inc に依頼しました。
        # robert は hulk の出版を macmillan.inc に依頼しました。
        # ken は captain_america の出版を academic.inc に依頼しました。
        # ken は iron_man の出版を blackwell.inc に依頼しました。
        # ken は spider_man の出版を blackwell.inc に依頼しました。
    """
    SELECT 
        `myapp_writer`.`id`, 
        `myapp_writer`.`name` 
    FROM 
        `myapp_writer` LIMIT 21;

    SELECT 
        `myapp_comic`.`id`, 
        `myapp_comic`.`title`, 
        `myapp_comic`.`writer_id`, 
        `myapp_comic`.`publisher_id`, 
        `myapp_publisher`.`id`, 
        `myapp_publisher`.`name` 
    FROM 
        `myapp_comic` 
    INNER JOIN 
        `myapp_publisher` 
    ON 
        (
            `myapp_comic`.`publisher_id` = `myapp_publisher`.`id`
        ) 
    WHERE 
        `myapp_comic`.`writer_id` IN (1, 2, 3, 4, 5);
    """
    return render(request, 'myapp/index.html')