- 技術 💻
- 設計
ER図の書き方|記号の読み方からCREATE TABLEまで実行して確認
ER図(Entity Relationship Diagram:実体関連図)とは、システムが扱うデータの「まとまり(エンティティ)」と、その間の「関係(リレーションシップ)」を図にしたものです。 データベースを作る前の設計図として、テーブルの構成や、どのデータがどのデータにひも付くかを関係者で共有するために使います。
ER図の解説は記号の紹介で終わるものが多いのですが、実際の開発ではER図を「CREATE TABLE」に変換して、制約として動かすところまでが設計です。この記事では、架空の「社内の備品貸出システム」を題材に、業務要件→エンティティ抽出→Mermaidで作図→DDL(テーブル定義のSQL)→PostgreSQL 16で実際に作成し、外部キー制約違反などを起こして確認した実行結果までを載せます。多対多の関係を中間テーブルに直す前後の比較や、既存のデータベースからER図を起こすSQLも紹介します。
ER図の書き方の早見表
| 手順 | やること | 成果 | ポイント |
|---|---|---|---|
| 1 | 業務要件を文章にする | 業務ルールの箇条書き | 「誰が・何を・いくつ」を書く |
| 2 | 名詞を拾ってエンティティにする | エンティティ一覧 | 画面項目ではなく「管理したいもの」 |
| 3 | 関係と数(カーディナリティ)を決める | 関係線と記号 | 両方向から「最小・最大」を読む |
| 4 | 多対多を中間テーブルに直す | 中間エンティティ | 中間テーブル自体が業務の意味を持つことが多い |
| 5 | 主キー・外部キー・属性を書く | 論理モデル | 識別子と属性を分ける |
| 6 | 型・制約・命名を決める | 物理モデル | NOT NULL、UNIQUE、CHECKまで |
| 7 | DDLにして実際に作る | CREATE TABLE文 | 制約違反を起こして確かめる |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「ER図の書き方|記号の読み方からCREATE TABLEまで実行して確認」
ER図の構成要素
ER図は、次の3つの要素でできています。
| 要素 | 意味 | 物理モデル(テーブル)での姿 |
|---|---|---|
| エンティティ | 管理したいもののまとまり(社員、備品、貸出など) | テーブル |
| 属性 | エンティティが持つ項目(氏名、資産番号など) | 列(カラム) |
| リレーションシップ | エンティティどうしの関係(社員が備品を借りる) | 外部キー(FOREIGN KEY) |
属性のうち、1件を特定するためのものを主キー(PK)、ほかのエンティティの主キーを参照するものを外部キー(FK)と呼びます。
論理モデルと物理モデル
| 観点 | 論理モデル | 物理モデル |
|---|---|---|
| 目的 | 業務のデータ構造を正しく表す | 特定のデータベースで実装する |
| 名前 | 日本語でもよい(社員、備品) | 英字の命名規則にそろえる(employees) |
| 型 | 書かない、または大まかに | varchar(50)、date などを正確に |
| 多対多 | 書いてもよい | 必ず中間テーブルに分解 |
| 制約 | 必須かどうか程度 | NOT NULL、UNIQUE、CHECK、インデックス |
| 主な読者 | 業務担当者、設計者 | 開発者、DB管理者 |
概念モデル(エンティティ名と関係だけの図)を最初に描き、論理→物理と詳しくしていく流れが一般的です。
記号の読み方:IE記法とIDEF1Xの対応表
日本の開発現場でよく使われる記法は、IE記法(カラスの足記法)とIDEF1Xの2つです。どちらも同じことを表せますが、記号が違います。
カーディナリティの対応表
カーディナリティとは、「相手1件に対して、こちらが最小何件・最大何件ひも付くか」を表す数のことです。
| 意味(相手1件あたり) | 表記 | IE記法(線の端) | IDEF1X(子の側の端) | Mermaid erDiagram |
|---|---|---|---|---|
| ちょうど1 | 1 | 縦線2本 ‖ | ●に「1」 | || |
| 0または1 | 0..1 | 丸+縦線 ○| | ●に「Z」 | |o または o| |
| 0以上 | 0..* | 丸+カラスの足 ○< | ●のみ | }o または o{ |
| 1以上 | 1..* | 縦線+カラスの足 |< | ●に「P」 | }| または |{ |
IE記法の線の端は2つの記号の組み合わせで、箱に近い記号が最大、線の内側の記号が最小を表します。「○」は0、「|」は1、「カラスの足」は多(many)です。たとえば、社員の箱の近くに「||」、貸出の箱の近くに「○<」があれば、「社員1人に対して貸出は0件以上、貸出1件に対して社員はちょうど1人」と読みます。
その他の記号の対応
| 意味 | IE記法 | IDEF1X | Mermaid |
|---|---|---|---|
| エンティティ | 四角 | 独立エンティティは角が四角、依存エンティティは角が丸い | 四角 |
| 識別関係(親のキーが子の主キーの一部) | 実線(ツールによる) | 実線 | -- |
| 非識別関係(親のキーが子の通常の列) | 破線(ツールによる) | 破線 | .. |
| 親が任意(子の外部キーがNULL可) | 親側に○ | 親側にひし形◇ | 親側に o |
読み方の練習
erDiagram
A ||--|| B : "1対1(必須)"
C |o--o| D : "0..1対0..1"
E ||--o{ F : "1対0以上"
G ||--|{ H : "1対1以上"
※WordPressでMermaidの図を表示するには、Mermaid対応のプラグインが必要です。
読むときは、必ず両方向から1文ずつ言葉にします。 「E 1件に F は0件以上」「F 1件に E はちょうど1件」。どちらか一方だけ読むと、最小値(0か1か)の間違いに気づきにくくなります。
業務要件からER図を作る(備品貸出システム)
ステップ1:業務ルールを文章にする
架空の「社内の備品貸出システム」の業務ルールです。
- 社員は、どこか1つの部署に所属する。部署には社員が0人以上いる
- 備品(ノートPC、プロジェクターなど)には資産番号が付いている
- 社員は備品を借り、返却予定日までに返す。同じ社員が何度でも借りられ、同じ備品も何度でも貸し出される
- 1台の備品を、同時に2人へ貸すことはできない
- 過去の貸出履歴も残したい
ステップ2:名詞からエンティティを抽出する
| 拾った名詞 | 判断 | 理由 |
|---|---|---|
| 社員 | エンティティ | 複数件あり、属性(氏名、メール)を持つ |
| 部署 | エンティティ | 複数件あり、社員から参照される |
| 備品 | エンティティ | 資産番号で識別して管理したい |
| 資産番号 | 属性(備品) | 備品を特定する項目 |
| 返却予定日 | 属性(?) | 社員にも備品にも属さない → 「貸出」の属性 |
| 貸出履歴 | エンティティ(貸出) | 「借りる」という出来事そのもの |
「どのエンティティにも置き場所がない属性」が出てきたら、関係そのものがエンティティになるサインです。返却予定日は「社員」にも「備品」にも置けず、「ある社員が、ある備品を、いつ借りたか」という出来事に付く属性です。
ステップ3:多対多を中間テーブルに直す(修正前→修正後)
素直に描くと、社員と備品は多対多になります。
修正前(論理的には正しいが、そのままではテーブルにできない)
erDiagram
EMPLOYEES }o--o{ EQUIPMENT : "借りる"
リレーショナルデータベースでは、多対多を直接表す列を作れません。よくある回避策と、その結果を比べます。
| 回避策 | 何が起きるか |
|---|---|
| 備品テーブルに「借りている社員ID」列を1つ置く | 履歴が残らない(返却したら上書き)。返却予定日の置き場もない |
| 社員テーブルに「借りている備品ID」をカンマ区切りで入れる | 外部キー制約がかけられず、検索・集計も難しい |
| 中間テーブル「貸出」を作る | 履歴が残り、返却予定日などの属性も置ける |
修正後:中間テーブル「LOANS(貸出)」を置き、1対多の関係2本に分解します。
erDiagram
DEPARTMENTS ||--o{ EMPLOYEES : "所属する"
EMPLOYEES ||--o{ LOANS : "借りる"
EQUIPMENT ||--o{ LOANS : "貸し出される"
DEPARTMENTS {
int department_id PK
varchar name UK "部署名"
}
EMPLOYEES {
int employee_id PK
int department_id FK
varchar name "氏名"
varchar email UK
}
EQUIPMENT {
int equipment_id PK
varchar asset_code UK "資産番号"
varchar name "品名"
}
LOANS {
int loan_id PK
int employee_id FK
int equipment_id FK
date loaned_at "貸出日"
date due_on "返却予定日"
date returned_at "返却日(NULL=貸出中)"
}
この図は、mermaid-cli 12.0.0(Mermaid 12.0.0)で描画できることを確認しています。
中間テーブルの主キーは、2つの外部キーの組み合わせ(複合主キー)にする方法と、代理キー(loan_id のような連番)を置く方法があります。同じ社員が同じ備品を何度も借りるので、(employee_id, equipment_id) の組み合わせは一意になりません。そのため、ここでは代理キーを主キーにしました。
ステップ4:「同時に2人へ貸せない」をどう表すか
「備品1台に対する貸出中の記録は0件か1件」というルールは、ER図の線の記号だけでは表せません。ER図には注記(ノート)で業務ルールとして書き、物理モデルで制約として実装します。PostgreSQLでは、条件付きの一意インデックス(部分インデックス)で表せます。
CREATE TABLEに変換する
ER図の要素とDDLの対応は、次のとおりです。
| ER図 | DDL |
|---|---|
| エンティティ | CREATE TABLE |
| 主キー(PK) | PRIMARY KEY |
| 外部キー(FK)と関係線 | REFERENCES 親テーブル(列) |
| 関係の最小値が1(必須) | 外部キー列に NOT NULL |
| 関係の最小値が0(任意) | 外部キー列を NULL 可にする |
| 一意(UK) | UNIQUE |
| 業務ルールの注記 | CHECK、部分一意インデックスなど |
CREATE TABLE departments (
department_id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name varchar(50) NOT NULL UNIQUE
);
CREATE TABLE employees (
employee_id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
department_id integer NOT NULL REFERENCES departments(department_id),
name varchar(50) NOT NULL,
email varchar(254) NOT NULL UNIQUE
);
CREATE TABLE equipment (
equipment_id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
asset_code varchar(20) NOT NULL UNIQUE,
name varchar(100) NOT NULL
);
CREATE TABLE loans (
loan_id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
employee_id integer NOT NULL REFERENCES employees(employee_id),
equipment_id integer NOT NULL REFERENCES equipment(equipment_id),
loaned_at date NOT NULL DEFAULT CURRENT_DATE,
due_on date NOT NULL,
returned_at date,
CHECK (due_on >= loaned_at)
);
-- 備品1台につき「貸出中(returned_at が NULL)」は0件か1件
CREATE UNIQUE INDEX loans_one_active_per_equipment
ON loans (equipment_id) WHERE returned_at IS NULL;
ER図の「社員 ||--o{ 貸出」の「||」(ちょうど1)が、employee_id integer NOT NULL REFERENCES ... の NOT NULL と REFERENCES に対応しています。関係線の記号と制約が1対1で対応しているかを見比べると、図とDDLのずれを見つけられます。
PostgreSQLで作成して制約違反を確かめる(実行結果)
上のDDLを PostgreSQL 16.13 で実行し、次のテストデータを投入しました。
INSERT INTO departments (name) VALUES ('開発部'), ('総務部');
INSERT INTO employees (department_id, name, email) VALUES (1, '山田', 'yamada@example.com');
INSERT INTO equipment (asset_code, name) VALUES ('PC-0001', 'ノートPC'), ('PJ-0001', 'プロジェクター');
INSERT INTO loans (employee_id, equipment_id, loaned_at, due_on) VALUES (1, 1, '2026-09-28', '2026-10-05');
-- 1: 存在しない社員
INSERT INTO loans (employee_id, equipment_id, loaned_at, due_on) VALUES (99, 2, '2026-09-28', '2026-10-05');
-- 2: 同じ備品を二重に貸し出す
INSERT INTO loans (employee_id, equipment_id, loaned_at, due_on) VALUES (1, 1, '2026-09-29', '2026-10-06');
-- 3: 貸出記録のある社員を削除
DELETE FROM employees WHERE employee_id = 1;
-- 4: 返却後なら再貸出できる
UPDATE loans SET returned_at = '2026-09-30' WHERE loan_id = 1;
INSERT INTO loans (employee_id, equipment_id, loaned_at, due_on) VALUES (1, 1, '2026-10-01', '2026-10-08');
SELECT loan_id, employee_id, equipment_id, loaned_at, returned_at FROM loans ORDER BY loan_id;
実行結果(psql の出力)です。
INSERT 0 2
INSERT 0 1
INSERT 0 2
INSERT 0 1
psql:/tmp/er_test.sql:7: ERROR: insert or update on table "loans" violates foreign key constraint "loans_employee_id_fkey"
DETAIL: Key (employee_id)=(99) is not present in table "employees".
psql:/tmp/er_test.sql:9: ERROR: duplicate key value violates unique constraint "loans_one_active_per_equipment"
DETAIL: Key (equipment_id)=(1) already exists.
psql:/tmp/er_test.sql:11: ERROR: update or delete on table "employees" violates foreign key constraint "loans_employee_id_fkey" on table "loans"
DETAIL: Key (employee_id)=(1) is still referenced from table "loans".
UPDATE 1
INSERT 0 1
loan_id | employee_id | equipment_id | loaned_at | returned_at
---------+-------------+--------------+------------+-------------
1 | 1 | 1 | 2026-09-28 | 2026-09-30
4 | 1 | 1 | 2026-10-01 |
(2 rows)
| テスト | 結果 | ER図のどこに対応するか |
|---|---|---|
| 1 存在しない社員で貸出 | 外部キー制約違反で拒否 | 「貸出1件に社員はちょうど1人」 |
| 2 貸出中の備品をもう一度貸出 | 部分一意インデックス違反で拒否 | 注記「同時に2人へ貸せない」 |
| 3 貸出履歴のある社員を削除 | 参照されているため拒否 | 親を消すと子が迷子になる関係 |
| 4 返却後に再貸出 | 成功 | 履歴は何件でも残せる(0..*) |
ER図に描いたルールが、データベースの制約として実際に守られることを確認できました。 なお、2件目の貸出の loan_id が 4 になっているのは、失敗した INSERT でも IDENTITY の採番(シーケンス)が進むためです。連番に欠番が出るのは仕様どおりの動きなので、「連番=件数」として扱わないようにしましょう。
テスト3のように、社員を削除できないのは困る場合もあります。そのときは、物理削除ではなく「退職日」列を追加して論理的に扱う、または ON DELETE の動作を決めるなど、業務ルールとして設計で決めておきます。
よくある誤りと直し方
| よくある誤り | 起きること | 直し方 |
|---|---|---|
| 最小値(0か1か)を片方向しか考えない | NOT NULL の付け忘れ、または付けすぎ | 両方向から1文ずつ読む |
| 多対多を物理モデルに残す | テーブルにできない | 中間テーブルを置き、名前を業務の言葉にする(「社員備品」より「貸出」) |
| 画面の項目をそのままエンティティにする | 同じデータが複数テーブルに重複 | 「管理したいもの」単位で考える |
| 計算で出せる値を属性にする(貸出件数など) | 更新漏れで値がずれる | 必要なときに集計する。性能上必要なら理由を注記 |
| 関係線はあるのに外部キー制約を付けない | 存在しない社員の貸出が入る | ER図の線とREFERENCESを1対1で対応させる |
| 業務ルールを図に書かない | 実装で漏れる | 線で表せないルールは注記に書き、CHECKなどに落とす |
| 論理名と物理名の対応表がない | 図とDDLを突き合わせられない | テーブル定義書に両方を書く |
重複をなくすための正規化の手順は、別記事「データベースの正規化」で詳しく扱っています。ER図を描き終えたら、正規化の観点でも見直すと安心です。
既存のデータベースからER図を起こす方法
運用中のシステムを引き継いだときなど、先にデータベースがあってER図がない場面もよくあります。
方法1:ツールのリバースエンジニアリング機能を使う
A5:SQL Mk-2、DBeaver、MySQL Workbench などは、接続したデータベースからER図を自動生成できます。
方法2:SQLで外部キーを取り出してMermaidにする
PostgreSQL のシステムカタログ(pg_constraint)から外部キーを取り出し、Mermaid の関係線の形で出力するSQLです。
-- 外部キーから Mermaid erDiagram の関係線を出力する
SELECT format(' %s ||--o{ %s : "%s"',
c.confrelid::regclass, -- 参照される側(親)
c.conrelid::regclass, -- 参照する側(子)
c.conname) AS mermaid_line
FROM pg_constraint c
WHERE c.contype = 'f'
AND c.connamespace = 'public'::regnamespace
ORDER BY 1;
上で作成したデータベースで psql -At を付けて実行した結果です。
departments ||--o{ employees : "employees_department_id_fkey"
employees ||--o{ loans : "loans_employee_id_fkey"
equipment ||--o{ loans : "loans_equipment_id_fkey"
先頭に erDiagram の1行を足せば、そのままMermaidの図になります。ただし、このSQLはすべての関係を「1対0以上」として出力します。外部キー列が NULL 可なら親側を |o(0..1)に、子が必ず1件以上ある業務ルールなら |{ に、といった調整は、\d テーブル名 で NOT NULL を確認しながら手で行います。
外部キー制約が付いていないデータベースもあります。 その場合は、列名(〜_id)の一致、アプリケーションのSQL、実データの突き合わせ(子の値がすべて親に存在するか)から関係を推定し、「推定」と明記してER図に描きます。
ER図作成ツールの比較
| ツール | 形式 | 得意なこと | 向いている場面 |
|---|---|---|---|
| Mermaid | テキスト(Markdown内) | Gitで差分管理、GitHubやドキュメントに埋め込み | 設計書と一緒にリポジトリで管理したい |
| dbdiagram.io | テキスト(DBML)+Web | 記述から図を即時表示、SQLの出力 | 素早く叩き台を作って共有したい |
| A5:SQL Mk-2 | Windowsアプリ | 既存DBからの逆生成、DDL生成、論理名・物理名の併記 | 国内の業務システム開発、既存DBの調査 |
| draw.io(diagrams.net) | GUI(XML保存) | 自由なレイアウト、注記の追加 | 説明用の図、DB以外の図と一緒に描く |
ER図とDDLのどちらを正本にするかを最初に決めると、図と実物のずれを防げます。たとえば「DDLを正本とし、ER図はリリースごとに逆生成する」「Mermaidを正本とし、レビューでDDLと突き合わせる」などです。
よくある質問
Q. ER図はIE記法とIDEF1Xのどちらで書けばよいですか? A. チームやお客さまの設計書標準に合わせるのが第一です。指定がなければ、IE記法(カラスの足)は読み手が多く、ツールの対応も広いので選ばれやすい記法です。どちらを使う場合も、凡例を図の近くに置くと読み違いを防げます。
Q. 中間テーブルの主キーは複合キーと連番のどちらがよいですか? A. 2つの外部キーの組み合わせが一意になるなら複合主キーが素直です。同じ組み合わせが何度も発生する(今回の貸出のような)場合は、連番などの代理キーを主キーにして、必要な一意性は UNIQUE 制約や部分インデックスで表します。
Q. ER図にはすべての列を書くべきですか? A. 目的によります。概念モデルや業務担当者との打ち合わせでは主キー・外部キーと代表的な属性だけに絞ると読みやすくなります。すべての列・型・制約はテーブル定義書やDDLで管理し、ER図は構造の把握に集中させる分担がおすすめです。
Q. ER図はいつ更新すればよいですか? A. テーブルや関係が変わるたびです。マイグレーション(DB変更のスクリプト)を追加するプルリクエストで、ER図の更新も同時にレビューするルールにすると、ずれが起きにくくなります。
まとめ
- ER図は、エンティティ・属性・リレーションシップでデータ構造を表す設計図です
- カーディナリティは両方向から「最小・最大」を1文ずつ読むと誤りに気づけます
- IE記法とIDEF1Xは記号が違うだけで、対応表を使えば読み替えられます
- どこにも置けない属性は、関係そのものがエンティティ(中間テーブル)になるサインです
- 関係線はREFERENCESとNOT NULLに、線で表せない業務ルールはCHECKや部分インデックスに落とします
- DDLを実際に流して制約違反を起こすと、ER図の意図どおりか確かめられます
- 既存DBからは、ツールの逆生成やシステムカタログのSQLでER図を起こせます
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「ER図の書き方|記号の読み方からCREATE TABLEまで実行して確認」
株式会社RJCでは、ER図とDDLを行き来しながらデータ設計を確かめるやり方を、プロジェクトの中で仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。