| この記事の要点 |
|
Django の結合の考え方
Django の ORM では、SQL の JOIN を直接書く代わりに、モデルどうしの関係をフィールドとして定義します。関係を定義しておけば、user.dept.dept_name のように属性をたどるだけで関連テーブルの値を参照できます。
ただし、何も指定しないと関連テーブルの値は参照した時点で別の SQL を発行して取得されます。一覧表示などで件数分の SQL が走る「N+1 問題」を避けるため、最初の SQL で JOIN させる select_related を使います。この記事では、ユーザー(user テーブル)と部署(dept テーブル)を結合する例で説明します。
モデル
ユーザー側に部署への外部キーを定義します。
from db_itn.models.dept import Dept
from django.db import models
class User(models.Model):
user_id = models.IntegerField(primary_key=True)
dept = models.ForeignKey(Dept, on_delete=models.DO_NOTHING)
class Meta:
db_table = 'user'
from django.db import models
class Dept(models.Model):
dept_id = models.IntegerField(primary_key=True)
dept_name = models.CharField(max_length=2000)
class Meta:
db_table = 'dept'
ForeignKey のフィールド名を dept にすると、データベース上の列名は自動的に dept_id になります。既存のテーブルで列名が違う場合は db_column='列名' で指定します。
ForeignKey と OneToOneField の違い
| フィールド | 関係 | 例 | 逆向きの参照 |
|---|---|---|---|
ForeignKey | 多対一(部署 1 つにユーザー複数) | ユーザーと所属部署 | dept.user_set.all() |
OneToOneField | 1 対 1(ユーザー 1 人にプロフィール 1 件) | ユーザーと詳細プロフィール | user.profile |
上の例は「ユーザーから見て部署が 1 つ」なので、ユーザー側から見れば 1 対 1 で値を取れますが、データモデルとしては多対一です。部署から所属ユーザーを一覧することもあるなら、ForeignKey のままで正解です。相手側も必ず 1 件だけと決まっている場合(ユーザーとプロフィールなど)は、OneToOneField を使うと重複登録をデータベースの一意制約で防げます。結合の書き方はどちらも同じです。
ビュー
users = User.objects.filter(user_id=1).select_related('dept')
select_related に関連フィールド名を渡すと、次のような JOIN 付きの SQL が 1 回だけ発行されます(実際の列名は省略しています)。
SELECT user.user_id, user.dept_id, dept.dept_id, dept.dept_name
FROM user
INNER JOIN dept ON (user.dept_id = dept.dept_id)
WHERE user.user_id = 1
dept フィールドに null=True を付けている場合は、部署が未設定のユーザーも取得できるよう LEFT OUTER JOIN になります。
結合先の列で絞り込みたいときは、__(アンダースコア 2 つ)で関連をたどります。この場合も内部結合の SQL が生成されます。
# 部署名が「営業部」のユーザー
users = User.objects.filter(dept__dept_name='営業部').select_related('dept')
# 必要な列だけ辞書で取得する場合
rows = User.objects.values('user_id', 'dept__dept_name')
テンプレート
filter() の結果は 1 件でも QuerySet(複数件の入れ物)なので、テンプレートでは for で取り出します。
{% for user in users %}
{{ user.user_id }}
{{ user.dept.dept_id }}
{{ user.dept.dept_name }}
{% endfor %}
user.user_id が user テーブルの列、user.dept.dept_id と user.dept.dept_name が dept テーブルの列です。1 件だけ取得したい場合は、ビューで User.objects.select_related('dept').get(user_id=1) とすれば、テンプレートで直接 {{ user.dept.dept_name }} と書けます。
注意点
- select_related は「1 つ」の側にだけ使える: ForeignKey・OneToOneField の順方向(と OneToOne の逆方向)が対象です。部署から所属ユーザー一覧のような「多」の側や多対多には
prefetch_relatedを使います(SQL は 2 回に分かれます) - DO_NOTHING の扱い:
on_delete=models.DO_NOTHINGは、部署が削除されても Django 側では何もしない設定です。データベースに外部キー制約があれば削除時にエラー、なければ存在しない部署を指すユーザーが残ります。新規に設計するならPROTECTやCASCADEなど、意図に合ったものを選びます - select_related の付け忘れ: 付けなくても動作はしますが、一覧で
user.deptを参照するたびに SQL が発行されます。件数が多いと急に遅くなる原因になります
確認方法
qs = User.objects.filter(user_id=1).select_related('dept')
print(qs.query) # 発行される SQL を表示
出力に INNER JOIN が含まれていれば結合されています。開発中は SQL ログを出力して、ページ表示時に発行される SQL の回数も確認しておくと、N+1 問題に早く気づけます。
関連
- SQL JOIN 完全ガイド (INNER/LEFT/RIGHT/FULL OUTER)
- Django Model 完全ガイド — フィールド型 / migrations / リレーション
- Django で SQL ログを出力する方法|django.db.backends と N+1 の発見
- SQL JOIN の ON と WHERE の違い 完全ガイド
子ページはありません
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- プロジェクトをTomcatプロジェクトとして認識させる方法 2026-10-07 22:32:50
- MySQLの1366 Incorrect string value|Laravelの文字コード・絵文字エラー 2026-10-07 21:54:03
- curlの証明書ホスト名不一致|旧エラー51・現行60の確認と対処 2026-10-07 21:54:03
- LaravelのMassAssignmentException|fillableの原因と安全な対処 2026-10-07 21:54:03
- Eclipse で Tomcat の起動ログがコンソールに出ない時の確認手順 2026-10-07 21:54:02
- MySQLにおける中央値(Median)の導き方(バージョン8未満) 2026-10-07 13:49:45
- getInputForward 2026-10-07 13:41:15
- JSONから配列に変換 2026-10-07 13:41:15
- ビューから値をモデルに格納しコントローラーで受け取る方法 2026-10-07 13:23:41
- Laravelのテーブル作成と定義変更|マイグレーション・up/down・注意点 2026-10-07 13:23:41
- NumPy 配列に要素を追加する方法 (append / concatenate) 2026-10-07 13:23:41
- MariaDB・MySQLで現在日時を取得する方法|NOW・タイムゾーン・保存型 2026-10-07 13:13:36
- 【django】テンプレートで定数を使用する方法 2026-10-07 13:10:15
- Spring BootにおけるApplication.propertiesの環境依存設定の分割方法 2026-10-07 12:09:35
- Not supported for DML operations【Springエラー】 2026-10-07 11:09:38