Claude Media
Claude CodeでER図をMermaidで起こす — 既存DBのDDLから

Claude CodeでER図をMermaidで起こす — 既存DBのDDLから

information_schemaの外部キーからMermaidのerDiagramを生成し、FKが無い暗黙の関連は命名規則から推測して別扱いにする手順を、Skillの書き方とともに示します。

既存データベースのER図は、DDLと外部キー制約を材料にすればClaude Codeに起こさせられます。ただし、外部キーが張られていない列の関連まで同じ線で描くと、図が実態より確かに見えてしまいます。この記事では、宣言された外部キーと命名規則からの推測を分けて描くMermaidのerDiagramを作る手順を示します。

流れは3段階です。information_schemaから外部キーを取り出し、Mermaidの記法に変換し、最後にFKの無い関連候補を別の印で足します。繰り返し使えるよう、Skillとしてまとめる書き方まで扱います。

先に決めておく描き分けのルール

ER図は、外部キー制約のあるDBでも無いDBでも、同じ記法で描けてしまいます。読む人は線が1本あれば制約があると受け取りがちです。そこで、図に出す関連を2種類に分けます。

  • 宣言済み: DDLにFOREIGN KEYがあり、information_schemaから取れるもの
  • 推定: 制約は無いが、列名やテーブル名から参照先が推測できるもの

Mermaidの属性には、PK・FK・UKのキー指定と、末尾の二重引用符で囲むコメントを付けられます。キーに使えるのはこの3つで、コメントの中に二重引用符は書けません。この仕様に合わせて、次のように使い分けます。

種類属性のキー関連線のラベル
宣言済みFK属性のキーFK を付ける関連線のラベル列名だけ(例: customer_id)
推定属性のキーキーを付けず、コメントに参照先関連線のラベル推定: を前置(例: 推定: coupon_id)

Mermaidの線の種類(実線と破線)は、別の意味に使われています。実線は識別関連(子が親なしに存在できない関係)、破線は非識別関連を表します。宣言済みか推定かの区別に線種を流用すると、モデリング上の意味とずれます。区別はラベルと属性コメントで行います。

材料になるDDLの例

以降の説明に使う、ECサイト風の小さなスキーマです。PostgreSQLを想定しています。

CREATE TABLE customers (
  id bigint PRIMARY KEY,
  email text NOT NULL UNIQUE,
  name text
);
 
CREATE TABLE coupons (
  id bigint PRIMARY KEY,
  code text NOT NULL
);
 
CREATE TABLE categories (
  id bigint PRIMARY KEY,
  name text NOT NULL
);
 
CREATE TABLE products (
  id bigint PRIMARY KEY,
  name text NOT NULL,
  category_id bigint,          -- 外部キー無し
  created_by bigint            -- 参照先が不明
);
 
CREATE TABLE orders (
  id bigint PRIMARY KEY,
  customer_id bigint NOT NULL REFERENCES customers (id),
  coupon_id bigint,            -- 外部キー無し
  status text NOT NULL
);
 
CREATE TABLE order_items (
  order_id bigint NOT NULL REFERENCES orders (id),
  product_id bigint NOT NULL REFERENCES products (id),
  quantity int NOT NULL,
  PRIMARY KEY (order_id, product_id)
);

外部キーが張られているのは3本(orders.customer_id、order_items.order_id、order_items.product_id)です。orders.coupon_idとproducts.category_idは名前から参照先が見当たるのに制約が無く、products.created_byは参照先らしいテーブルがありません。実務の古いDBでも、このように制約の抜けが混在していることが多くなります。

information_schemaから外部キーを取り出す

宣言済みの外部キーは、information_schemaの3つのビューで追えます。

  • key_column_usage: 一意制約・主キー・外部キーで制限される列の一覧です。外部キーの行では、position_in_unique_constraintに参照先の列が、その一意制約の中で何番目かが入ります
  • referential_constraints: 外部キー制約の一覧で、参照先の一意制約の名前(unique_constraint_name)を持ちます
  • 参照先の列は、key_column_usageをもう一度、参照先の制約名で引くと分かります

複合キーでも列の対応がずれないよう、ordinal_positionとposition_in_unique_constraintを突き合わせます。

SELECT
  kcu.table_name   AS child_table,
  kcu.column_name  AS child_column,
  pk.table_name    AS parent_table,
  pk.column_name   AS parent_column,
  kcu.constraint_name
FROM information_schema.referential_constraints rc
JOIN information_schema.key_column_usage kcu
  ON kcu.constraint_schema = rc.constraint_schema
 AND kcu.constraint_name   = rc.constraint_name
JOIN information_schema.key_column_usage pk
  ON pk.constraint_schema  = rc.unique_constraint_schema
 AND pk.constraint_name    = rc.unique_constraint_name
 AND pk.ordinal_position   = kcu.position_in_unique_constraint
WHERE kcu.table_schema = 'public'
ORDER BY kcu.table_name, kcu.constraint_name, kcu.ordinal_position;

先ほどのDDLなら、結果は3行です。

 child_table  | child_column | parent_table | parent_column
--------------+--------------+--------------+---------------
 order_items  | order_id     | orders       | id
 order_items  | product_id   | products     | id
 orders       | customer_id  | customers    | id

権限で行が欠けることがある

information_schemaのビューは、現在のユーザーが見られる範囲に絞られます。referential_constraintsは参照元テーブルへの書き込み権限(所有者か、SELECT以外の何らかの権限)がある制約だけを返します。key_column_usageも、所有者か権限を持つ列しか出しません。

読み取り専用のユーザーで接続すると、外部キーがあるのに結果に出ないテーブルが生じえます。その状態で図を起こすと、実在する外部キーが「推定」にも「無し」にも落ちます。スキーマの全体像を取るときは、オーナー権限か、DDLを書き出したファイル(pg_dump --schema-onlyの出力など)を使うほうが確実です。MySQLでも同じビュー名を使い、REFERENCED_TABLE_NAMEとREFERENCED_COLUMN_NAMEがkey_column_usageに直接入っているため、自己結合は要りません。

Claude CodeにMermaidへ変換させる

材料が揃ったら、Claude Codeへ依頼します。接続先のDBに直接つなぐ構成はMySQL MCPサーバーを読み取り専用で使う手順にあります。ここでは、クエリ結果とDDLのファイルを渡す形で進めます。

psql "$DATABASE_URL" -At -F ',' -f fk.sql > fk.csv
pg_dump --schema-only --no-owner "$DATABASE_URL" > schema.sql
claude

セッション内では、たとえば次のように頼みます。

fk.csv が宣言済みの外部キー、schema.sql が DDL。
この2つから Mermaid の erDiagram を er.mmd に書いて。
- 外部キーの列には FK を付ける
- カーディナリティは NOT NULL なら ||、NULL 可なら |o を親側に
- 子側は、DDL から最小件数が分からないので o{ にする
- FK が無い列は、まだ描かない

最後の1行が要点です。推定を混ぜるのは第2段階に分けると、宣言済みの部分が正しいかどうかを先に確かめられます。

生成されるMermaidの形

宣言済みの3本だけを描くと、例えば次のような形になります(Mermaid公式の記法に沿った例で、Claudeの実出力を貼ったものではありません)。

erDiagram
  customers ||--o{ orders : "customer_id"
  orders ||--o{ order_items : "order_id"
  products ||--o{ order_items : "product_id"
 
  customers {
    bigint id PK
    text email UK
    text name
  }
  orders {
    bigint id PK
    bigint customer_id FK
    bigint coupon_id
    text status
  }
  order_items {
    bigint order_id PK, FK
    bigint product_id PK, FK
    int quantity
  }
  products {
    bigint id PK
    text name
    bigint category_id
    bigint created_by
  }

order_itemsの2列は、主キーでも外部キーでもあるのでPK, FKとカンマで並べます。複数のキー指定をカンマで区切る書き方は、Mermaidの仕様に載っています。

線の端の記号は、外側が最大値、内側が最小値を表します。||は「ちょうど1」、o{は「0以上」です。DDLから分かるのは「外部キー列がNOT NULLか」までで、「注文には必ず明細が1件以上ある」といった最小件数は読み取れません。子側を|{(1以上)と書かせると、DDLに無い業務ルールを断定することになります。

FKの無い暗黙の関連を推測する

次に、第2段階です。制約の無い列のうち、参照先を推測できるものを拾います。Claude Codeには、判断の根拠が見える形で出させます。

schema.sql のうち、FOREIGN KEY が無い列を洗い出して。
- `<単数形のテーブル名>_id` で、同名の主キーを持つテーブルが1つだけあるものは「候補」
- テーブルが無い、または複数あるものは「不明」
- 列の型が参照先の主キーの型と一致しているかも書く
結果は表で。図にはまだ反映しない。

先ほどのDDLなら、次のような振り分けになります。

列判定根拠
orders.coupon_id判定候補: coupons.id根拠coupon に対応する coupons があり、型は共に bigint
products.category_id判定候補: categories.id根拠複数形の categories が対応し、型も一致
products.created_by判定不明根拠名前から参照先を決められず、users 相当のテーブルも無い

命名規則による推測は外れます。created_byのように規則に乗らない列があり、規則に乗っていても別の意味の列ということもあります。表に「不明」の行を残す理由は、推測できないものを推測できないまま見せるためです。

人間が表を確認して「候補」を承認したものだけを、図へ足します。足し方は、ラベルに推定:を前置し、属性のコメントに参照先を残す形です。

erDiagram
  coupons |o--o{ orders : "推定: coupon_id"
  categories |o--o{ products : "推定: category_id"
 
  orders {
    bigint coupon_id "推定: coupons.id"
  }
  products {
    bigint category_id "推定: categories.id"
    bigint created_by "参照先不明"
  }

親側が|o(0または1)なのは、列がNULLを許すためです。上の図に、この2本の線を足し、ordersとproductsの属性行をコメント付きのものに置き換えます。

Skillにして使い回す

毎回この依頼文を打つのは手間なので、Skillにまとめます。スキルは~/.claude/skills/<名前>/SKILL.mdに置くと全プロジェクトで、プロジェクトの.claude/skills/<名前>/SKILL.mdに置くとそのリポジトリで読み込まれます。

---
name: ddl-to-er
description: DDLと外部キー一覧から Mermaid の erDiagram を作る。宣言済みFKと命名規則からの推定を分けて描く。
disable-model-invocation: true
argument-hint: [schema.sql] [fk.csv]
---
 
$0 (DDL) と $1 (外部キー一覧) から er.mmd を書く。
 
1. 外部キー一覧にある関連だけを、FK 付きで描く。
2. NOT NULL の外部キー列は親側 ||、NULL 可は |o。子側は o{ 固定。
3. FK の無い `*_id` 列は、推定候補の表を先に出して確認を取る。
4. 承認された候補だけを「推定:」ラベルと属性コメントで足す。
5. 参照先が決まらない列は、描かずにコメントで「参照先不明」と残す。

フロントマターのdisable-model-invocation: trueは、Claudeが自動で呼び出さず、/ddl-to-er schema.sql fk.csvと打ったときだけ動くようにする指定です。引数は$0・$1のように位置で受け取れます。

シェルの出力をスキルの本文に差し込む!`<command>`という書き方もあり、実行結果がClaudeに渡る前にプロンプトへ埋め込まれます。fk.csvを事前に作らず、!`psql "$DATABASE_URL" -f fk.sql`の形で外部キー一覧を直接差し込む運用もできます。この場合、接続先のDBに対する読み取りがスキルの呼び出しごとに走るので、接続文字列の権限は読み取り専用に絞ってください。

出力を確かめるチェックリスト

生成されたer.mmdは、次の観点で点検します。Mermaidの構文エラーは、描画してみるまで気づけません。描画の確認にClaudeの会話内でMermaidを表示したいときは、Mermaid Chartコネクタが使えます。

  • 件数: 宣言済みの関連の数が、クエリ結果の行数と一致している(複合キーは1本の制約が複数行になるので、制約名で数える)
  • 外部キー列: FKの付いた列が、クエリ結果のchild_columnと過不足なく一致している
  • 推定の漏れ込み: 推定:の付かない線が、クエリ結果に無い関連になっていない
  • カーディナリティ: 子側がo{のまま、親側だけがNOT NULLに応じて変わっている
  • 型の整合: 推定の候補で、列の型が参照先の主キーと食い違っていない

件数の照合は、シェルで機械的にできます。fk.csvの行数(単一列の外部キーだけなら制約数)と、er.mmdの宣言済みの線の数を並べます。

wc -l < fk.csv
grep -E '\|o?--o\{' er.mmd | grep -vc '推定'

2つの数字が食い違ったら、Claudeにどちらの関連が足りないか、または余っているかを挙げさせます。複合キーがあるときは、行数ではなく制約名の種類数と比べます。

テーブルが数十を超える場合は、1枚の図に押し込まず、機能ごとにエンティティを絞って複数の.mmdに分ける方が読めます。Mermaidにはdirectionでレイアウトの向きを変える指定がありますが、エンティティの数自体は減らせないので、分割はSkillの手順側に入れておきます。

スキーマの変更履歴をコードで管理しているなら、図はDDLから毎回作り直す使い捨てにするのが安全です。手で直した図とDDLのずれは、見た目では分かりません。Prismaのようにスキーマ定義が先にある構成はClaude CodeでPrismaスキーマを設計するで扱っています。

MySQLで同じ一覧を取る

MySQLのkey_column_usageには、参照先のスキーマ・テーブル・列(REFERENCED_TABLE_SCHEMA・REFERENCED_TABLE_NAME・REFERENCED_COLUMN_NAME)がはじめから入っています。参照先を引くための自己結合が要らないので、クエリは短くなります。

SELECT
  table_name             AS child_table,
  column_name            AS child_column,
  referenced_table_name  AS parent_table,
  referenced_column_name AS parent_column,
  constraint_name
FROM information_schema.key_column_usage
WHERE table_schema = 'shop'
  AND referenced_table_name IS NOT NULL
ORDER BY table_name, constraint_name, ordinal_position;

外部キーの行だけに絞る条件がreferenced_table_name IS NOT NULLです。主キーや一意制約の行は参照先の列が空なので、ここで落ちます。shopは例のスキーマ名なので、自分のDB名に置き換えます。

複合キー・自己参照・癖のある名前の扱い

機械的に変換すると崩れやすい箇所が3つあります。Skillの手順に先に書いておくと、手直しが減ります。

  • 複合外部キー: 1本の制約が複数行で返ります。Mermaidの関連線は制約1本につき1本にし、ラベルには列名を+でつなぎます(例: "tenant_id+user_id")。列ごとに線を引くと、線の数が実際の制約数と合いません
  • 自己参照: 親と子が同じテーブルの外部キー(parent_idなど)も、categories |o--o{ categories : "parent_id"の形で書けます。親側を|oにしないと、ルートのカテゴリが存在できない図になります
  • 名前の癖: エンティティ名には任意のユニコード文字が使えますが、空白を含むときは二重引用符で囲む必要があります。日本語のテーブル名やスペース入りの名前は、囲んでおくと安全です

スキーマ名付きのテーブルが複数あるDBでは、エンティティ名をpublic_ordersのようにスキーマ名と結んで衝突を避けます。同名のテーブルが別スキーマにあると、図の上で同じ箱に見えるためです。

まとめ

ER図の線は、外部キーという事実に基づくものと、列名からの推測に基づくものを混ぜないことで信頼できる資料になります。information_schemaで宣言済みの外部キーを確定させ、推測は人間の承認を挟んで別の印を付け、参照先が決まらない列は描かずに残す。この3点をSkillに固定しておけば、DBが変わるたびに同じ品質で図を作り直せます。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/skills
  2. mermaid.js.org/syntax/entityRelationshipDiagram.html
  3. postgresql.org/docs/current/infoschema-key-column-usage.html
  4. postgresql.org/docs/current/infoschema-referential-constraints.html
  5. dev.mysql.com/doc/refman/8.4/en/information-schema-key-column-usage-tabl…
この記事を共有:XはてブLinkedIn