ブログ

ER図の書き方:要件からダイアグラムまで

ER図は、順番さえ押さえれば思ったより早く書けます。要件からエンティティを拾い、リレーションを決め、キーを確定して、記法に落とす——これだけです。課題で初めて ER図を書く方でも、実務で初めて設計を任された方でも、この順番は変わりません。

文章で読むだけでは身につかないので、掲示板DBを1つ、最初から最後まで一緒に設計してみましょう。記事の最後には、そのまま実行できる DDL と、自分の設計を自分で点検できるチェックリストも用意しています。

手順1: 要件からエンティティを拾う

手が止まったら、まず要件の文章の名詞に印を付けるところからです。

会員が投稿を作成する。投稿にはコメントが付く。投稿は1つのカテゴリに属する。

会員、投稿、コメント、カテゴリ。この4つがエンティティの候補です。

もちろん、名詞がすべてエンティティになるわけではありません。基準は1つだけ覚えれば足ります。複数件を保存して個々を区別するならエンティティ、何かを説明する1つの値なら属性です。

要件に出てきそうな名詞をこの基準で仕分けると、こうなります。

名詞 判定 理由
会員、投稿、コメント エンティティ 複数件が蓄積され、個々を識別する必要がある
カテゴリ エンティティ 一覧として管理され、複数の投稿から共有される
タイトル、作成日時 属性 投稿1件を説明する値
コメント数 どちらでもない 数えれば出る集計値 — 保存した瞬間に整合性の管理対象になる

「コメント数」のように計算で得られる値は、まず外すのが原則です。性能のために保存する非正規化は、必要性が証明されてからでも遅くありません。

「管理者」のようなロールも迷いやすいところです。ロールの種類が少なく付随情報もないなら会員の role 属性で十分で、ロールごとに権限リストなどのデータが付き始めたら、そのときエンティティに昇格させれば大丈夫です。

設計レビューで最もよく見かける失敗が、まさにこの部分です。カテゴリをエンティティに分けず、投稿テーブルに category VARCHAR(50) として埋め込んでしまうパターンで、最初は問題なく動くので誰も気づきません。

ある日カテゴリ名を1つ変更することになり、数万件の投稿を UPDATE しながらようやく気づくのです。複数の行が同じ値を共有しているなら、分離のサインだと受け取るのが安全です。

手順2: リレーションとカーディナリティを決める

エンティティが揃ったら、次は動詞の番です。「作成する」「付く」「属する」がそれぞれリレーションになります。あとは両側に何件が対応するかを決めるだけです。

  • 会員1人が投稿を複数書く → 会員 1 : N 投稿
  • 投稿1件にコメントが複数付く → 投稿 1 : N コメント
  • カテゴリ1件に投稿が複数属する → カテゴリ 1 : N 投稿

投稿とタグのように両側とも複数になると話が変わります。リレーショナルデータベースは N:M を直接保存できないため、post_tags のような中間テーブルを1つ置いて 1:N 2本に分解します。基本情報技術者試験でも定番の論点なので、手順だけでなく理由まで覚えておくと後で効きます。

見落としやすいのは最小値です。コメントが1件も付いていない投稿もあり得るので、投稿から見たコメントは 0..N。細かい話に見えますが、この違いが後で NOT NULL を付けるか、LEFT JOIN を使うかを分けます。

一歩先へ:早い段階で出会う3つのバリエーション

1対1のリレーション。 会員と会員プロフィールのように、1件にちょうど1件だけ対応する関係もあります。ログインのたびに読む情報と、たまにしか読まない自己紹介・設定を分けたいときや、機密情報をアクセス権の異なるテーブルに隔離したいときに登場します。ただし、ダイアグラムに 1:1 がやたら多いなら、分けなくてよいテーブルを割っていないか先に疑ってみてください。

属性を持つ N:M。 post_tags に「タグ付けした日時」「タグ付けした人」が付いた瞬間、中間テーブルは単なる接続ではなく、自分の意味を持ったエンティティになります。そうなったら、名前も機械的な A_B 連結ではなく意味の伝わるものに変えるのがおすすめです。

自己参照リレーション。 返信コメント(スレッド)が代表例です。comments に自分自身を参照する parent_id を追加し、最上位コメントには親がいないので NULL を許可します。この記事の実例では簡潔さのために外しましたが、実務の掲示板ならほぼ確実に出会う構造です。

手順3: 属性とキーを確定する

主キーは迷わずサロゲートキー(id)をおすすめします。メールアドレスを主キーにするとどうなるかというと——会員がアドレスを変更した瞬間、それを参照しているすべての箇所が一緒に揺らぎます。

「ISBN のように絶対変わらない自然キーならよいのでは」という反論もありますが、実務での「絶対」は思ったより頻繁に崩れます。メールアドレスには UNIQUE 制約を付けておけば、重複防止という実益はそのまま確保できます。

外部キーの置き場所は機械的に決まります。常にリレーションの N 側です。会員 1 : N 投稿なら、posts テーブルが member_id を持ちます。

中間テーブルの主キーには2つの流儀があります。(post_id, tag_id) の複合主キーなら同じ組み合わせの重複を構造的に防げますし、別途 id を置いて2カラムに UNIQUE を張れば、他のテーブルからこの行を参照しやすくなります。どちらも間違いではなく、中間テーブルを他から参照する予定があるかで決めれば十分です。

データ型は今の段階で完璧に決めようとしなくて構いません。設計が一周してから整えるほうが、結果的に早く進みます。

削除ポリシーまで決めて、設計は完成する

リレーションを引き終えたら、1本ずつ問いかけるべき質問があります。「親が消えたら、子はどうなるのか?」

会員が退会したら、その会員の投稿とコメントも一緒に消えるべきでしょうか。ON DELETE CASCADE を深く考えずに付けた結果、会員を1人削除しただけで投稿とコメントが連鎖的に消えてしまう事故は、思っているより起きています。

基本は RESTRICT で止めておき、本当に一緒に消えるべき関係にだけ CASCADE を明示するのが安全です。掲示板なら、会員の行を消す代わりに非アクティブに切り替えるソフトデリートもよく使われる選択肢です。

IE記法(カラスの足)の読み方

記法の名前がいくつも出てきて身構えがちですが、実務で出会うのはほぼ IE(Information Engineering)だけです。線の端の記号がカーディナリティを表します。

  • | — ちょうど1
  • — 0(なくてもよい)
  • カラスの足(三つ叉) — N(複数)

実際の線の端には、この記号が2つ組み合わさって付きます。|○ は「0 または 1」、|< は「1 以上」、○< は「0 以上」と読みます。

私たちの掲示板で練習してみましょう。

  • 会員 ─ 投稿:会員側の端は ||、投稿側の端は ○< — 「投稿は必ず会員1人のもの。会員は投稿が0件のことも複数のこともある」
  • カテゴリ ─ 投稿:同じ形 — 「投稿は必ず1つのカテゴリに属する」

実線は識別リレーション、破線は非識別リレーションですが、最初はこの区別よりカラスの足の向きを正確に読めることが先です。参考までに、課題ではひし形でリレーションを描く Chen 記法を指定されることがあり、Oracle 系の文書では Barker 記法も見かけます。自分で描く機会は少なくても、読めるようにしておくと役立ちます。記号4種類の全体と3記法の比較は、読み取り練習付きでER図の記号まとめに整理してあります。

名前を2種類で管理する習慣も、この段階で身につけておく価値があります。人が読む論理名(会員、投稿)と、DBに入る物理名(members, posts)を併記しておくと、企画者と開発者が同じ図を見ながら会話できます。ER図ツールが Logical / Physical ビューを分けて提供しているのはこのためで、テーブル定義書を出力するときにもこの2つの名前がそのまま活きてきます。

実例: 掲示板DBを DDL に落とす

ここまで決めた内容を MySQL の DDL にするとこうなります。コピーしてそのまま実行できます。

CREATE TABLE members (
  id         BIGINT AUTO_INCREMENT PRIMARY KEY,
  email      VARCHAR(255) NOT NULL UNIQUE,
  nickname   VARCHAR(50)  NOT NULL,
  created_at DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE categories (
  id   INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50) NOT NULL UNIQUE
);

CREATE TABLE posts (
  id          BIGINT AUTO_INCREMENT PRIMARY KEY,
  member_id   BIGINT NOT NULL,
  category_id INT    NOT NULL,
  title       VARCHAR(200) NOT NULL,
  content     MEDIUMTEXT   NOT NULL,
  created_at  DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT fk_posts_member   FOREIGN KEY (member_id)   REFERENCES members (id),
  CONSTRAINT fk_posts_category FOREIGN KEY (category_id) REFERENCES categories (id)
);

CREATE TABLE comments (
  id         BIGINT AUTO_INCREMENT PRIMARY KEY,
  post_id    BIGINT NOT NULL,
  member_id  BIGINT NOT NULL,
  content    TEXT     NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT fk_comments_post   FOREIGN KEY (post_id)   REFERENCES posts (id),
  CONSTRAINT fk_comments_member FOREIGN KEY (member_id) REFERENCES members (id)
);

この DDL には、いくつか意図的な選択が入っています。

  • 投稿・コメントの id は BIGINT、カテゴリは INT — 増える速度が違うテーブルに同じサイズを与える理由はありません。
  • 本文は MEDIUMTEXT — TEXT の 64KB 上限は、長文では思ったより早く到達します。コメントは TEXT で十分です。
  • created_at DEFAULT CURRENT_TIMESTAMP — アプリケーションが入れ忘れても記録は残ります。
  • MySQL(InnoDB)は外部キーのカラムに自動でインデックスを作るため、「この会員の投稿一覧」というよくある検索は追加作業なしでインデックスが効きます。

posts.member_id が「会員 1 : N 投稿」の N 側外部キーです。手順2で決めたリレーションと外部キーの位置が1つでもずれているなら、それは設計ではなく、ただの絵になってしまっています。

先に進む前のセルフチェックリスト

設計を終えたら、ダイアグラムを前にこれだけ確認してみてください。レビュアーがいなくても、この6種類のチェックだけで大きな事故は防げます。

  • すべてのテーブルに主キーがあるか
  • 外部キーがすべてリレーションの N 側にあるか
  • 中間テーブルなしの N:M が残っていないか — 1つのカラムにカンマ区切りで値を詰めていたら危険信号
  • 数えれば出る値を保存していないか
  • 命名ルールが1つに統一されているか(単数/複数、大文字小文字、区切り文字)
  • リレーションごとに削除ポリシーを決めたか

どのツールで書くか

ホワイトボードから始めるのは良いやり方です。問題はその後です。

汎用の作図ツールで管理し続けると、図と実際のスキーマが少しずつずれていき、いつからか誰も図を信用しなくなります。だからこそ、カラム同士を直接つなげて DDL と行き来できる専用ツールが結局は楽です。チームで共有してレビューを受けることまで考えると、なおさらです。図がそのままスキーマになり、テーブル定義書も同じデータから出力できます。

上の実例のように DDL が手元にあるなら、WorksCove ERD に貼り付けるだけで掲示板のダイアグラムが出来上がります。

掲示板DDLの貼り付けで自動生成されたER図(WorksCove ERD)

このツールを作る前は、私たちも作図ツールと Excel の定義書を行き来しながら二重管理をしていました。リリースを重ねるうちに、図が実際のスキーマと合わない箇所がひとつまたひとつと増え、会議のたびに「図ではなく DB を見よう」と言われる状態になりました。

ダイアグラムがスキーマそのものでなければ、いつか必ずずれる——WorksCove ERD を DDL ベースで作った理由です。

よくある質問

記法はどれを使えばよいですか?

実務では IE記法(カラスの足)が事実上の標準です。課題で Chen 記法を指定されている場合を除き、IE記法で書くことをおすすめします。

多対多(N:M)のリレーションはどう書きますか?

リレーショナルデータベースは N:M を直接保存できません。両テーブルの主キーを外部キーとして持つ中間テーブルを作り、1:N のリレーション2本に分解して書きます。

テーブル名は単数形と複数形のどちらがよいですか?

どちらが正解というより、チーム内で統一することが大切です。複数形(members, posts)は user のような予約語と衝突しにくいため、実務ではやや多く使われています。いずれにせよ、1つのプロジェクトの中で混在させないことが重要です。

すでに運用中のDBがあります。ER図を最初から書く必要はありますか?

必要ありません。DDL をエクスポートして ER図ツールに貼り付ければダイアグラムが自動生成されます。リバースエンジニアリング対応のツールなら、DBに直接接続して取り込むこともできます。

順番をもう一度まとめると、エンティティ → リレーション → キー → 記法です。掲示板の例で流れがつかめたら、あとは自分の要件で同じ手順をもう一周するだけです。

その次が気になったら、テーブルをどこまで分割するかを決める正規化、そして完成したスキーマをドキュメントにするテーブル定義書の自動化が、自然な次のテーマです。