ER図とは(概念・論理・物理の違い)
ER図(Entity-Relationship Diagram)はシステムが扱うデータの構造(エンティティと関係)を表した設計図です。基本設計・詳細設計の段階に応じて3つのレベルがあります。
| 種別 | 作成フェーズ | 記載内容 | 特徴 |
|---|---|---|---|
| 概念ER図 | 基本設計 | エンティティ名・リレーション・カーディナリティ | DBに依存しない。業務担当者も読める抽象度 |
| 論理ER図 | 詳細設計 | テーブル名・全カラム名・データ型・PK/FK | DBに依存しない。実装に近い抽象度 |
| 物理ER図 | 詳細設計〜実装 | 実際のSQL DDL・インデックス・パーティション | 特定DBMS(PostgreSQL等)の構文で記載 |
💡 基本設計では概念ER図を完成させる
基本設計フェーズの目標は「概念ER図の完成」です。テーブルの詳細なカラム定義(データ型・桁数・NULL可否)は詳細設計(論理ER図)で行います。基本設計段階で詳細まで定義しようとすると、業務要件の変更で大幅な手戻りが発生します。
① エンティティの抽出方法
エンティティは「システムが永続化すべきデータの塊」です。業務フロー図・ユースケース図・機能一覧表を入力として、以下の観点でエンティティを抽出します。
- 業務上の「モノ(名詞)」:顧客・商品・注文・社員・部門など
- 業務上の「出来事(イベント)」:受注・出荷・入金・ログインなどのトランザクション
- 「モノ」と「出来事」の関連エンティティ:受注明細(受注×商品)など
| エンティティ名 | 論理名 | 種別 | 説明 |
|---|---|---|---|
| 顧客 | Customer | マスタ | システムを利用する顧客の基本情報 |
| 商品 | Product | マスタ | 販売する商品の基本情報・価格 |
| カテゴリ | Category | マスタ | 商品を分類するカテゴリ(階層あり) |
| 受注 | Order | トランザクション | 顧客からの注文ヘッダ情報(受注日・顧客・合計金額) |
| 受注明細 | OrderDetail | トランザクション | 受注に紐づく商品・数量・単価の明細 |
| 在庫 | Stock | トランザクション | 商品別・倉庫別の在庫数量 |
| 操作ログ | OperationLog | ログ | ユーザーの操作履歴(監査証跡) |
② リレーションとカーディナリティの定義
エンティティ間の関係(リレーション)とカーディナリティ(1対1・1対N・N対N)を定義します。
| 親エンティティ | 子エンティティ | カーディナリティ | オプショナリティ | 関係の説明 |
|---|---|---|---|---|
| 顧客 | 受注 | 1 : N | 顧客は0件以上の受注を持つ | 1人の顧客が複数の受注を持つ |
| 受注 | 受注明細 | 1 : N | 受注は1件以上の明細を持つ | 1つの受注に複数の商品明細 |
| 商品 | 受注明細 | 1 : N | 商品は0件以上の明細に含まれる | 1つの商品が複数の明細に登場 |
| カテゴリ | 商品 | 1 : N | 商品は必ず1つのカテゴリに属する | カテゴリ別に商品を分類 |
| カテゴリ | カテゴリ | 1 : N | (自己参照)親カテゴリ→子カテゴリ | カテゴリの階層構造(parent_id) |
⚠️ N対Nリレーションは中間テーブルで解決する
「商品」と「タグ」のようなN対Nリレーションが発生した場合、リレーショナルDBでは中間テーブル(例:m_product_tag)を定義して解決します。N対Nのまま設計を進めると、後工程でテーブル再設計が必要になります。
③ 正規化方針の定義
正規化方針をプロジェクト標準として定義します。一般的には第3正規形(3NF)を目標とし、性能要件がある場合に意図的な非正規化(反正規化)を行います。
- 第1正規形(1NF):繰り返し項目の排除。各カラムに原子的な値のみ格納
- 第2正規形(2NF):部分関数従属の排除。複合PK時に非キー属性がPK全体に依存
- 第3正規形(3NF):推移的関数従属の排除。非キー属性が他の非キー属性に依存しない
意図的な非正規化(反正規化)の例:受注明細テーブルに product_name(商品名)を冗長に持たせることで、商品マスタ更新後も受注時点の商品名を保持できます。この判断は設計段階で明示的に記録します。
④ テーブル種別の分類
テーブルを種別で分類することで、共通設計ルール(論理削除・タイムスタンプ)の適用基準が明確になります。
| 種別 | 説明 | 命名プレフィックス | 共通カラム |
|---|---|---|---|
| マスタテーブル | 比較的変化しない基本データ(顧客・商品・組織) | m_ | created_at, updated_at, delete_flg |
| トランザクションテーブル | 業務処理の記録(受注・出荷・入金) | t_ | created_at, created_by, updated_at, updated_by |
| コードテーブル | コード値・区分値の定義(ステータス・区分) | c_ | sort_order |
| ログテーブル | 操作ログ・変更履歴(監査証跡) | l_ | logged_at, user_id, action |
| ワークテーブル | バッチ処理中間データ・一時データ | w_ | created_at, status |
⑤ テーブル命名規則の定義
テーブル名・カラム名の命名規則をプロジェクト標準として定義します。
- テーブル名:スネークケース・英小文字(
m_product) - 主キー名:
テーブル名_id形式(product_id) - 外部キー名:参照先テーブルのPK名と同じ(
customer_id) - フラグカラム:
_flgサフィックス・BOOLEAN型(delete_flg) - 日時カラム:
_atサフィックス(created_at・updated_at) - ユーザーIDカラム:
_byサフィックス(created_by・updated_by)
Python Tips — 既存DBからER図情報を自動取得する
既存システムのリプレース案件では、現行DBのスキーマ情報をPythonで自動取得してER図作成の出発点とすることができます。
"""
PostgreSQLのinformation_schemaからテーブル構造とFK関係を取得してER図作成に活用する。
pip install psycopg2-binary pandas
"""
import psycopg2
import pandas as pd
from contextlib import contextmanager
DB_CONFIG = {
"host": "localhost",
"port": 5432,
"dbname": "mydb",
"user": "myuser",
"password": "mypassword",
}
@contextmanager
def get_connection():
conn = psycopg2.connect(**DB_CONFIG)
try:
yield conn
finally:
conn.close()
def get_tables(schema: str = "public") -> pd.DataFrame:
"""スキーマ内の全テーブル一覧を取得"""
sql = """
SELECT
t.table_name,
obj_description(c.oid, 'pg_class') AS table_comment,
pg_total_relation_size(c.oid) AS table_size_bytes
FROM information_schema.tables t
JOIN pg_class c ON c.relname = t.table_name
WHERE t.table_schema = %s
AND t.table_type = 'BASE TABLE'
ORDER BY t.table_name;
"""
with get_connection() as conn:
return pd.read_sql(sql, conn, params=(schema,))
def get_foreign_keys(schema: str = "public") -> pd.DataFrame:
"""外部キー関係を取得(ER図のリレーション情報)"""
sql = """
SELECT
tc.table_name AS child_table,
kcu.column_name AS child_column,
ccu.table_name AS parent_table,
ccu.column_name AS parent_column,
tc.constraint_name
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage ccu
ON ccu.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
AND tc.table_schema = %s
ORDER BY tc.table_name, kcu.column_name;
"""
with get_connection() as conn:
return pd.read_sql(sql, conn, params=(schema,))
def export_er_info(schema: str = "public") -> None:
"""テーブル一覧とFK情報をExcelに出力"""
tables = get_tables(schema)
fks = get_foreign_keys(schema)
# テーブルサイズを人間が読みやすい形式に変換
tables["table_size"] = tables["table_size_bytes"].apply(
lambda b: f"{b/1024/1024:.1f} MB" if b >= 1024*1024 else f"{b/1024:.1f} KB"
)
print(f"テーブル数: {len(tables)}")
print(f"FK関係数: {len(fks)}")
print("\n=== FK関係(ER図のリレーション)===")
print(fks.to_string(index=False))
with pd.ExcelWriter("er_info.xlsx") as writer:
tables[["table_name", "table_comment", "table_size"]].to_excel(
writer, sheet_name="テーブル一覧", index=False
)
fks.to_excel(writer, sheet_name="FK関係(リレーション)", index=False)
print("\ner_info.xlsx に出力しました")
if __name__ == "__main__":
export_er_info("public")
✅ eralchemy2 でER図を自動生成する
pip install eralchemy2 をインストールすると、eralchemy2 -i "postgresql+psycopg2://user:pass@host/db" -o er.png コマンドで既存DBのER図PNG/PDFを自動生成できます。既存システム分析の初期調査に非常に有効です。
定義チェックリスト
| チェック項目 | 確認ポイント |
|---|---|
| □ 全エンティティが抽出されているか | 機能一覧・業務フロー図の全データ項目がエンティティとして定義されているか |
| □ 全リレーションが定義されているか | エンティティ間の関係にカーディナリティとオプショナリティが明記されているか |
| □ N対Nリレーションが中間テーブルで解決されているか | N対NリレーションはER図に中間テーブルとして定義されているか |
| □ 正規化方針が文書化されているか | 第3正規形を目標としているか。意図的な非正規化の根拠が記録されているか |
| □ テーブル命名規則が定義されているか | テーブル名・カラム名の命名ルールが標準化されているか |
| □ 共通カラムが定義されているか | created_at・updated_at・delete_flgなどの共通カラムルールが定義されているか |