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_atupdated_at
  • ユーザーIDカラム_by サフィックス(created_byupdated_by

Python Tips — 既存DBからER図情報を自動取得する

既存システムのリプレース案件では、現行DBのスキーマ情報をPythonで自動取得してER図作成の出発点とすることができます。

Python — PostgreSQLからテーブル・FK関係を取得
"""
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などの共通カラムルールが定義されているか