ORM

Django ORMでリレーション先のデータを取得|ForeignKeyの順方向アクセス

DjangoのORMでは、ForeignKeyで紐づけられたモデル間のデータに簡単にアクセスできます。ここでは、多側(子モデル)から1側(親モデル)のデータを取得する順方向アクセスの方法を解説します。

基本的な使い方

views.py
model = Person.objects.select_related('company').all().values('company__name')

説明

Step 1select_relatedの基本

Djangoでは、以下の形式で多側のモデルから1側のモデルのデータを効率的に取得することができます:

多側のモデル.objects.select_related('1側のモデルのフィールド名').get(条件)
多側のモデル.objects.select_related('1側のモデルのフィールド名').filter(条件)
多側のモデル.objects.select_related('1側のモデルのフィールド名').all()

例えば、Personモデル(多側)からCompanyモデル(1側)のデータを取得する場合:

# Personに関連づけられたCompanyも同時に取得
person = Person.objects.select_related('company').get(id=1)

# 関連Companyのデータにアクセス
company_name = person.company.name

Step 2N+1問題とselect_related

本来、値取得のたびに取得するモデルのSQLの発行がされますが、select_relatedを使用すると、多側のモデル取得時に1側のモデルのデータも同時に取得するため、複数回SQLが発行されることを防ぐことができます。

多対1の関係というのは、会社(1)に対して人(多)が存在するという関係のことを言います。なので、人側から見ると会社は1個に特定されるので、紐づいたモデルは1つとなる関係です。

ポイント

N+1問題とは? 必要以上にSQLが発行されパフォーマンスが悪くなる問題のことです。例えば、10人のPersonデータを取得し、それぞれの所属Companyを参照する場合、1回のPersonsテーブルへのクエリと10回のCompanyテーブルへのクエリが発行されてしまう状況を指します(合計11回=N+1回)。

Step 3select_relatedとvaluesの組み合わせ

select_relatedとvaluesを組み合わせて、必要なフィールドだけを取得することもできます:

多側のモデル.objects.select_related('1側のモデルのフィールド名').values('1側のモデルのフィールド名__1側のフィールド名')

例:

# Personモデルと紐づいたCompanyモデルのnameフィールドを取得
persons = Person.objects.select_related('company').values('id', 'name', 'company__name')

# 結果例: [{'id': 1, 'name': '山田太郎', 'company__name': '株式会社A'}, ...]

上の例では、Personモデルと紐づいたCompanyモデルのnameフィールドの値を取得しています。

Step 4繰り返し処理での活用

select_relatedの効果は、for文でデータを繰り返し処理するときに特に顕著になります:

# select_relatedを使わない場合(N+1問題が発生)
persons = Person.objects.all()
for person in persons:
    print(f"{person.name}は{person.company.name}に所属しています")  # 各ループでSQLが発行される

# select_relatedを使う場合(効率的)
persons = Person.objects.select_related('company').all()
for person in persons:
    print(f"{person.name}は{person.company.name}に所属しています")  # 追加のSQLは発行されない

Step 5個別アクセスの例

取得したデータの中から特定のレコードにアクセスする場合も、select_relatedの恩恵を受けられます:

# 取得したクエリセットから最初のデータの関連Companyにアクセス
persons = Person.objects.select_related('company').all()
first_person_company_name = persons[0].company.name

このような形式でも紐づいているモデルにアクセスすることができます。

Step 6実践的な使用例

views.pyでのselect_relatedの使用例:

from django.shortcuts import render
from .models import Person

def person_list(request):
    # 関連Companyデータも一緒に取得
    persons = Person.objects.select_related('company').all()
    
    return render(request, 'persons/list.html', {
        'persons': persons
    })

def person_detail(request, person_id):
    # 個別データ取得時も関連Companyデータを効率的に取得
    person = Person.objects.select_related('company').get(id=person_id)
    
    return render(request, 'persons/detail.html', {
        'person': person
    })

テンプレートでの使用例(list.html):

<h1>社員一覧</h1>

<table>
    <tr>
        <th>ID</th>
        <th>名前</th>
        <th>所属会社</th>
    </tr>
    {% for person in persons %}
        <tr>
            <td>{{ person.id }}</td>
            <td>{{ person.name }}</td>
            <td>{{ person.company.name }}</td>  <!-- 追加SQLなしでアクセス可能 -->
        </tr>
    {% endfor %}
</table>
補足:
  • select_relatedは多対1(ForeignKey)または1対1(OneToOneField)の関係でのみ使用できます。
  • 1対多または多対多の関係には、prefetch_relatedを使用します。
  • 複数の関連モデルを取得する場合は、select_related('model1', 'model2')のように指定できます。
  • ネストした関連も取得できます:select_related('company__industry')

まとめ

  • ForeignKeyフィールド名でドットアクセスすると、関連する親モデルのインスタンスを取得できる
  • インスタンス.FK名.フィールド名で親モデルの各フィールドにアクセスできる
  • select_related()を使うとJOINで一括取得し、N+1問題を解消できる
  • filter内でFK名__フィールド名の形式で関連モデルの条件を指定できる
  • 複数段のリレーションも__で連結してアクセス可能