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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?