【Django】BookモデルからAuthorDetailの情報を取得する2つの方法（related_name、select_related、prefetch_related）

# 概要
Djangoにて、`Book`、`Author`、`AuthorDetail`が定義されたモデルがあるとします。

この時、`Book`モデルへのアクセス一回で著者の`biography`の値まで取得するにはどうすればいいでしょうか。
Django ORMで用意された`select_related`と外部キーを利用することで、簡単に`AuthorDetail`モデルまでアクセスすることができます。

今回は2通りの方法を紹介します。

## `models.py`と`related_name`について
`models.py`は以下の前提です。

```models.py
class Book(models.Model):
    book_id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=255, null=True)
    author_id = models.ForeignKey('Author', on_delete=models.CASCADE, null=True, db_column='author_id')
    genre_id = models.ForeignKey('Genre', on_delete=models.CASCADE, null=True, db_column='genre_id')

    class Meta:
        db_table = 'book'


class Author(models.Model):
    author_id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=255, null=True)

    class Meta:
        db_table = 'author'


class Genre(models.Model):
    genre_id = models.AutoField(primary_key=True)
    genre_name = models.CharField(max_length=100, null=True)

    class Meta:
        db_table = 'genre'


class AuthorDetail(models.Model):
    author_detail_id = models.AutoField(primary_key=True)
    author_id = models.ForeignKey(Author, related_name='author_details', on_delete=models.CASCADE, null=True, db_column='author_id')
    biography = models.TextField(null=True)

    class Meta:
        db_table = 'author_detail'
```

補足すると、`related_name`はdjangoのmodel間のリレーションシップにおいて逆方向のリレーションを参照するための名前を指定するために使用されます。関連フィールドが簡単に取得できますよ、というもの。

https://docs.djangoproject.com/ja/3.0/topics/db/queries/#following-relationships-backward


念の為、注意点としては、同じmodel内では`related_name`の名前は一意である必要があります。複数のフィールドに対して同じ名前を指定することができません。
また、特に指定しない場合は`<model_name>_set`という名前がデフォルトです。

それでは、主題の「`Book`モデルから`biography`を取得する」方法について考えてみます。

私がDjango5.1で検証したところ、以下の2通りで実現できました。

### ①`related_name`を利用してフィールドにアクセスする方法



```views.py
from book.models import Book

## 省略 ##

query_set = Book.objects.all()
data = query_set.select_related(
       'author_id',
       'genre_id'
       ).prefetch_related(
       'author_id__author_details'
       ).values(
       'book_id',
       'title',
       'author_id',
       'genre_id',
       'author_id__author_details__biography'
       )
```

`models.py`で定義した`related_name`を使用して関連オブジェクトにアクセスする際は、`select_related`ではなく`prefetch_related`を使用する必要があります。

それぞれ以下の違いがあります。

`select_related`：外部キーを持つ単一の関連オブジェクトに対して使用
`prefetch_related`：多対多や逆方向の外部キーに対して使用

`related_name`は逆方法の外部キーに対して使用されるものなので、`prefetch_related`で取得します。

繋がったルートを感覚的に書くとこんな感じです。

「(`Book`の)フィールド名__(そのカラムを持つ親モデルの中で定義された)リレーション名__(そのモデルが持つ)フィールド名」

`prefetch_related`を使用することで、関連オブジェクトを効率的に取得できる（データベースクエリを減らすことができる）ので、パフォーマンス重視の際はこちらの方が良いのではないでしょうか。

### ②モデル名を直接利用してフィールドにアクセスする方法

```views.py
from book.models import Book,

## 省略 ##

query_set = Book.objects.all()
data = query_set.select_related(
       'author_id',
       'genre_id'
       ).values(
       'book_id',
       'title',
       'author_id',
       'genre_id',
       'author_id__authordetail__biography'
       )
```

上記の方法でもアクセス可能でした。

この場合は、`authordetail`と指定しているように、モデル名を直接使用しています。

「(`Book`の)フィールド名__(そのカラムを持つ親の)モデル名__(そのモデルが持つ)フィールド名」

という感じなので、直感的にはこちらの方がルックアップのチェーンっぽくて読みやすい気もしますね。

ただし、こちらの場合は`prefetch_related`を使用していないため、関連オブジェクトの取得が非効率になることがあります。モデル名が長くなると何が書いてあるのかわかりづらいこともありますし。
ちなみにここでは`biography`を指定していますが、デフォルトでは主キーになっているようです。


以上、DjangoのORMでリレーションシップを利用して、DBアクセス一回で別のモデルの情報を取得する方法でした。