この用語ページの主要トピックを一覧から飛べます。
🍰 まずはやさしく
データの設計図のようなものです。
データベースの構造を整理するために使います。
スマホアプリのデータ管理などに役立ちます。
図を作るための3つの要素について学びます。
🍰 まずはやさしく
データベースを作るための重要な道具です。
データの関係を絵にして整理するために使います。
部活の名簿などを効率よく作る時に便利です。
図の書き方や種類について詳しく読みます。
論文・業務文書で 「ER 図」「ER モデル」「Entity-Relationship Diagram」「ERD」「データモデル」「概念設計」「論理設計」 といった表現が出てきたら、 このページです。
ER 図はデータベース設計の 最重要ツール。 業務要件をヒアリングして、 「どんなデータが」「どう関係しているか」を絵にする。 これなしに DB を設計すると、 後で「テーブルが増えすぎて管理不能」になります。
本ページでは ER 図の歴史、 記法の違い、 描き方、 正規化との関係、 ツール、 そして SSDSE データを RDB に格納する設計例まで網羅します。 NoSQL の文脈での進化(document、 graph データベース)にも触れます。
🍰 まずはやさしく
組織図と家系図を合わせたようなものです。
データのつながりを一目で分かるようにします。
図書館で本を借りる仕組みなどを例に考えます。
パズルの設計図のように考える方法を読みます。
ER 図を 「組織図」と「家系図」の合体と例えると分かりやすい。 組織図は「箱」と「線」で組織構造を表す。 ER 図は「エンティティ(箱)」と「リレーションシップ(線)」でデータ構造を表す。
図書館の業務を IT 化する。 ヒアリングしたら:
これを ER 図にすると:
[会員] --借りる(0..*)-- [貸出記録] --(*..1)-- [本] --書いた(*..*)-- [著者]
|
-属する(*..1)- [ジャンル]
これだけ描けば、 「会員テーブル」「本テーブル」「貸出記録テーブル」「著者テーブル」「ジャンルテーブル」と、 多対多を解消する「著者本テーブル(関連テーブル)」が必要、 と一目で分かる。
ER 図は 立体パズルの設計図。 各エンティティはパーツ、 リレーションシップは「どう組み合わさるか」。 設計図なしに作ると、 後で「ピースが合わない」事態に。 描いてから組むのが鉄則。
SSDSE-B-2026 は 1 つの大きな CSV ですが、 これを RDB に格納するなら:
[Prefecture] --(1..*)-- [YearlyStat] --属する(*..1)-- [Category]
| |
都道府県名 年度
地域コード 人口・出生数・etc.
この設計だと、 1 県あたり 1 行ではなく「県 × 年度」で複数行になり、 経年変化を見やすい形に正規化される。 ER 図がなければこの判断は出てこない。
ER 図はエンティティ(実体)と関係(リレーションシップ)を視覚化する。 ここでは「カーディナリティ・3 種類のリレーション・正規化までの流れ」を概念図で確認する。
「ER 図 → 正規化 → CREATE TABLE → INSERT → 結合クエリ」までを SSDSE-B-2026 で一気通貫で示す。 ER 図は「絵」のままだと半分の理解しかない。
このコードでやること: 都道府県マスタ、 年度マスタ、 統計値テーブルを正規化して作成。 PK / FK 制約を全て明示する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | import sqlite3 con = sqlite3.connect(':memory:') con.executescript(""" CREATE TABLE pref ( pref_code TEXT PRIMARY KEY, pref_name TEXT NOT NULL UNIQUE, region TEXT ); CREATE TABLE year ( year INTEGER PRIMARY KEY ); CREATE TABLE stat ( pref_code TEXT NOT NULL, year INTEGER NOT NULL, pop_total INTEGER, deaths INTEGER, PRIMARY KEY (pref_code, year), FOREIGN KEY (pref_code) REFERENCES pref(pref_code), FOREIGN KEY (year) REFERENCES year(year) ); """) print('テーブル作成完了') for row in con.execute("SELECT name FROM sqlite_master WHERE type='table'"): print(' -', row[0]) |
💬 3 つのテーブルが ER 図通りに作成された。 PK 複合キー(pref_code, year)が stat に効く。
このコードでやること: CSV を読み、 正規化された 3 テーブルに分割投入する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=0) # 2 行目の日本語名の行を落とし、都道府県コードの行だけ残す df = df[df['Code'].astype(str).str.match(r'^R\d{5}$', na=False)].copy() # 都道府県コードは 'Code' 列。'SSDSE-B-2026' 列は年度 df = df.rename(columns={'Code': 'pref_code', 'Prefecture': 'pref_name'}) df['SSDSE-B-2026'] = pd.to_numeric(df['SSDSE-B-2026'], errors='coerce') for _c in ['A1101', 'A4200']: df[_c] = pd.to_numeric(df[_c], errors='coerce') # pref マスタ pref = df[['pref_code', 'pref_name']].drop_duplicates() pref['region'] = pref['pref_name'].map({'北海道':'北海道', '青森県':'東北'}).fillna('その他') pref.to_sql('pref', con, if_exists='append', index=False) # year マスタ year = pd.DataFrame({'year': sorted(df['SSDSE-B-2026'].unique())}) year.to_sql('year', con, if_exists='append', index=False) # stat 事実テーブル stat = df[['pref_code', 'SSDSE-B-2026', 'A1101', 'A4200']].rename( columns={'SSDSE-B-2026':'year','A1101':'pop_total','A4200':'deaths'}) stat.to_sql('stat', con, if_exists='append', index=False) print(f'pref: {con.execute("SELECT COUNT(*) FROM pref").fetchone()[0]} rows') print(f'year: {con.execute("SELECT COUNT(*) FROM year").fetchone()[0]} rows') print(f'stat: {con.execute("SELECT COUNT(*) FROM stat").fetchone()[0]} rows') |
💬 47 都道府県 × 12 年度 = 564 行が事実テーブルに入り、 ER 図の関係が物理化された。
このコードでやること: pref と stat を JOIN し、 2023 年の死亡率 TOP5 を取得する。
1 2 3 4 5 6 7 8 9 10 11 12 | q = """ SELECT p.pref_name, s.pop_total, s.deaths, ROUND(1000.0 * s.deaths / s.pop_total, 2) AS death_rate_permille FROM stat s JOIN pref p ON s.pref_code = p.pref_code WHERE s.year = 2023 ORDER BY death_rate_permille DESC LIMIT 5 """ print(pd.read_sql(q, con)) |
💬 FK で結合した結果、 「県名 + 統計値」が 1 行に揃う。 これが ER 図 → 正規化の核心。
このコードでやること: 参照整合性違反を意図的に発生させ、 FK 制約が効くことを確認。
1 2 3 4 5 6 | con.execute('PRAGMA foreign_keys = ON') try: con.execute("INSERT INTO stat VALUES ('R99999', 2023, 1000, 50)") print('NG: 制約違反が許された') except sqlite3.IntegrityError as e: print(f'OK: 参照整合性違反を検知 → {e}') |
💬 ER 図に基づく FK 制約が物理レイヤで正しく効いている。 これがあれば「存在しない県コード」が紛れ込まない。
| 正規形 | 条件 | SSDSE での該当 |
|---|---|---|
| 第 1 正規形 | 属性が原子値 | 全列スカラー |
| 第 2 正規形 | 部分関数従属除去 | pref_name は pref に分離 |
| 第 3 正規形 | 推移関数従属除去 | region は pref に集約 |
教材の定番例「学生・科目・成績」で、 リレーション「履修」の 多重度(カーディナリティ)を 1:1 / 1:N / N:M に切り替えると、 クロウフット(鳥の足, IE 記法)の記号と「意味」がどう変わるかを体感する。 実体(エンティティ)=長方形、 関連=線+多重度記号で描く。 N:M は直接テーブル化できないので、 ボタンで 連関(中間)テーブル「履修」へ分解できる。 主キー(PK)・外部キー(FK)のハイライトで対応を確認しよう。
🍰 まずはやさしく
図を描くための共通のルールです。
誰が見ても同じ意味に伝わるように使います。
買い物サイトの注文データなどを整理する時に使います。
図で使う記号や線の意味について読みます。
ER 図には「数式」よりも「記法の規則」があります。 主要記法を整理。
| 関係 | Chen | IE (クロウフット) | UML |
|---|---|---|---|
| 1 対 1 | 1:1 | │ ── │ | 1..1 |
| 1 対 多 | 1:N | │ ── Ϟ | 1..* |
| 多対多 | M:N | Ϟ ── Ϟ | *..* |
| 0 または 1 | 0..1 | ○│ | 0..1 |
| 1 以上 | 1..N | │Ϟ | 1..* |
| 0 以上 | 0..N | ○Ϟ | 0..* |
親エンティティに依存し、 単独では存在できない。 二重線の四角で表記。 例:「貸出記録」は「会員」と「本」がないと存在しない(が、 これは関連テーブルで表現するのが現代的)。
2 つのエンティティ A, B の関係多重度は、 4 つの問いで決まる:
「ER 図」は単なる絵ではなく、 厳密な意味を持つ記号体系です。 1 つ 1 つを正確に理解しないと、 後の DB 設計で破綻します。
エンティティ(Entity)は、 業務上識別できる「もの」「人」「事象」を指します。 「会員」「本」「貸出記録」など。 物理的なものに限らず、 「予約」「契約」「イベント」のような抽象概念も含む。 ポイントは「同じ種類の複数の実体(インスタンス)」を抱える「型」であること。
リレーションシップ(Relationship)は、 エンティティ間の 「関係」を表します。 動詞で命名するのが定石(「借りる」「所属する」「書く」)。 重要なのは 多重度(cardinality):
多対多関係を物理 DB に落とすには、 関連テーブル(associative entity, junction table)が必須です。 例:「学生 ─ 履修 ─ 講義」と、 中間に履修テーブルを作る。 履修テーブルの PK は (学生 ID, 講義 ID) の複合主キー。 さらに「履修日」「成績」などの履修固有の属性をそこに置く。 これを ER 図上で「履修」エンティティとして明示的に描くのが論理設計のポイント。
ER 図を描いた後、 各エンティティに対し 正規化を適用:
読み取り性能のために、 あえて正規形を崩すことがあります(非正規化)。 「同じデータが複数箇所にコピーされる」リスクと、 「JOIN なしで読める」メリットのバランス。 DWH や OLAP では非正規化が定石(スタースキーマ、 スノーフレーク)。
SSDSE-B-2026 を RDB に格納するための ER 図設計を具体的に行います。
SSDSE-B-2026 は CSV 1 枚で、 行=年度×都道府県(47 県 ×1 年 = 47 行)、 列=112 個の指標(人口、 出生数、 病院数、 etc.)。 これを「1 テーブル」で持つのは正規化的に NG。
[Prefecture] --(1..*)-- [YearlyStat] --(*..1)-- [Year]
|
多くの数値属性
案 B は「指標が増えてもテーブル構造を変えずに済む」柔軟性が高いが、 SELECT に JOIN が必要で複雑。 案 A は単純だが列数が爆発する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | CREATE TABLE prefectures ( pref_code CHAR(6) PRIMARY KEY, pref_name VARCHAR(20) NOT NULL, region VARCHAR(20) ); CREATE TABLE indicators ( indicator_code VARCHAR(10) PRIMARY KEY, name VARCHAR(100), unit VARCHAR(20), description TEXT ); CREATE TABLE observations ( id BIGSERIAL PRIMARY KEY, pref_code CHAR(6) REFERENCES prefectures(pref_code), year SMALLINT, indicator_code VARCHAR(10) REFERENCES indicators(indicator_code), value DOUBLE PRECISION, UNIQUE (pref_code, year, indicator_code) ); CREATE INDEX idx_obs_pref_year ON observations(pref_code, year); CREATE INDEX idx_obs_indicator ON observations(indicator_code); |
1 2 3 4 5 6 7 8 9 10 | -- 47 都道府県の 2023 年高齢化率トップ 10 SELECT p.pref_name, obs.value AS aging_rate FROM observations obs JOIN prefectures p ON obs.pref_code = p.pref_code WHERE obs.indicator_code = 'AGING_RATE' AND obs.year = 2023 ORDER BY obs.value DESC LIMIT 10; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) # wide → long indicators = ['A1101', 'A1303', 'B4101', 'F3101', 'I510120'] long_df = df.melt( id_vars=['SSDSE-B-2026', 'Code', 'Prefecture'], value_vars=indicators, var_name='indicator_code', value_name='value' ) long_df.rename(columns={'SSDSE-B-2026': 'year', 'Code': 'pref_code'}, inplace=True) long_df.to_csv('observations.csv', index=False) print(long_df.head()) print(f'rows: {len(long_df)}') |
「県別+年度」検索が多いなら (pref_code, year) の複合インデックス。 「指標別」検索なら (indicator_code) 単独インデックス。 全部にインデックスを張ると INSERT が遅くなる。 トレードオフ。
将来 SSDSE-B-2027, 2028 が出ても、 observations テーブルに INSERT するだけで済む。 これが 正規化された ER 設計の強み。
合成データで n×m 関係を中間表で正規化する場合のテーブル数を計算する。
| 関係 | 表数 (含中間) |
|---|---|
| 学生-科目 (n:m) | 3 |
| 顧客-商品 (n:m) | 3 |
| 論文-著者 (n:m) | 3 |
| 部署-プロジェクト (n:m) | 3 |
1 2 3 4 5 6 7 | n_relations = 4 entities = 2 * n_relations junction = n_relations total = entities + junction print(f"エンティティ: {entities}") print(f"中間表: {junction}") print(f"合計表数: {total}") |
💬 手計算 (Step 2) 12 と Python 出力が完全一致。
SSDSE-B-2026 用のテーブル構造を SQLAlchemy ORM で定義する。 Python コードが ER 図と直接対応。
SQLAlchemy 2.x、 PostgreSQL 接続情報。
Python クラス定義、 CREATE TABLE 文が自動生成される。
「コード ⇄ ER 図」の双方向同期。 Alembic でマイグレーション自動化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | from sqlalchemy import create_engine, Column, String, Integer, Float, ForeignKey from sqlalchemy.orm import declarative_base, relationship Base = declarative_base() class Prefecture(Base): __tablename__ = 'prefectures' pref_code = Column(String(6), primary_key=True) pref_name = Column(String(20), nullable=False) region = Column(String(20)) observations = relationship('Observation', back_populates='prefecture') class Indicator(Base): __tablename__ = 'indicators' indicator_code = Column(String(10), primary_key=True) name = Column(String(100)) unit = Column(String(20)) observations = relationship('Observation', back_populates='indicator') class Observation(Base): __tablename__ = 'observations' id = Column(Integer, primary_key=True, autoincrement=True) pref_code = Column(String(6), ForeignKey('prefectures.pref_code')) year = Column(Integer) indicator_code = Column(String(10), ForeignKey('indicators.indicator_code')) value = Column(Float) prefecture = relationship('Prefecture', back_populates='observations') indicator = relationship('Indicator', back_populates='observations') engine = create_engine('postgresql://user:pass@localhost/ssdse') Base.metadata.create_all(engine) |
既存 DB から ER 図を自動生成する。 リバースエンジニアリングと呼ばれる。
DB 接続情報、 対象スキーマ。
ER 図(PNG / SVG)、 テーブル一覧。
巨大なレガシー DB の構造把握に有効。 ただし命名規則の悪い DB だと自動図も読みにくい。
1 2 3 4 5 6 7 8 | # pgAdmin の手順(GUI 操作): # 1. 対象 DB に接続 # 2. Schema → Right click → Generate ERD # 3. PDF / PNG で保存 # CLI 版(schemaspy): # java -jar schemaspy.jar -dp postgresql.jar -t pgsql \ # -host localhost -db ssdse -u user -p pass -o ./erd_out |
Markdown 互換の Mermaid 記法で、 ER 図をテキストで管理。 GitHub・Notion で自動レンダリング。
テキストエディタ。 専用ツール不要。
ER 図の SVG。
git diff で変更履歴が追える。 ドキュメントを「コード化」する DevOps 文化の一環。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | erDiagram PREFECTURE ||--o{ OBSERVATION : has INDICATOR ||--o{ OBSERVATION : measured PREFECTURE { string pref_code PK string pref_name string region } INDICATOR { string indicator_code PK string name string unit } OBSERVATION { int id PK string pref_code FK int year string indicator_code FK float value } |
無料の Web GUI ツールで ER 図を視覚的に作成。 IE 記法・Chen 記法・UML どれも対応。
ブラウザ、 Google アカウント(保存用、 任意)。
.drawio / SVG / PNG ファイル。
テキストエディタが苦手な人向け。 チームで共同編集も可(Google Drive 連携)。
1 2 3 4 5 6 7 8 9 10 11 | <!-- drawio はテキストベースの XML を内部で持つ --> <mxfile> <diagram> <mxGraphModel> <root> <mxCell vertex="1" value="Prefecture\npref_code (PK)\npref_name\nregion"/> <mxCell vertex="1" value="Observation\nid (PK)\npref_code (FK)\nyear\nvalue"/> </root> </mxGraphModel> </diagram> </mxfile> |
SQLAlchemy モデルの変更を、 DB マイグレーションスクリプトに自動変換。 ER 図の進化を git で管理。
SQLAlchemy モデル変更後の状態。
versions/xxx_add_region.py のようなマイグレーション。
本番 DB へ alembic upgrade head で適用。 ロールバックも可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | # alembic init alembic # 設定ファイル env.py に Base.metadata を登録 # モデル変更後: # alembic revision --autogenerate -m "add region column" # alembic upgrade head # 出力例(versions/abc123_add_region.py): def upgrade(): op.add_column('prefectures', sa.Column('region', sa.String(length=20))) def downgrade(): op.drop_column('prefectures', 'region') |
PR で ER 図 (.md) を変更したら、 GitHub Actions が自動でレンダリング・テキスト diff をコメント。
.github/workflows/erd-render.yml、 Mermaid ファイル。
PR コメントに最新 ER 図画像、 変更点リスト。
レビュアーが「絵」で差分を確認できる。 ドキュメント=コード文化の典型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | name: ERD Render on: pull_request: paths: ['docs/erd.md'] jobs: render: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: neenjaw/compass-mermaid-action@v1 with: mermaid-files: docs/erd.md - run: | gh pr comment ${{ github.event.pull_request.number }} \ --body "Updated ER diagram → $ARTIFACT_URL" |
ER 図を中心とした概念ツリー:
データモデリング ├─ 概念モデリング │ ├─ ER 図 (ERD) ← この用語 │ │ ├─ Chen 記法 │ │ ├─ IE 記法 (Crow's Foot) │ │ ├─ Barker 記法 │ │ └─ IDEF1X │ ├─ UML クラス図 │ └─ ファクト指向モデリング (ORM) ├─ 論理モデリング (正規化) └─ 物理モデリング (DBMS 固有) 派生・関連: ├─ ディメンショナルモデリング (DWH) │ ├─ スタースキーマ │ └─ スノーフレークスキーマ ├─ Data Vault モデリング ├─ NoSQL 設計パターン │ ├─ ドキュメント (MongoDB) │ ├─ KVS (Redis, DynamoDB) │ ├─ カラムストア (Cassandra) │ └─ グラフ (Neo4j) └─ Event Sourcing
ER 図の歴史を整理。
エドガー・F・コッドが「A Relational Model of Data for Large Shared Data Banks」を発表。 リレーショナルデータベースの理論的基盤を確立。
Peter Chen が論文「The Entity-Relationship Model — Toward a Unified View of Data」を発表。 「エンティティ」「リレーションシップ」「属性」の 3 要素で DB 構造を表現する手法を提唱。 ER 図の誕生。
James Martin の Information Engineering で「クロウフット(鳥の足)」記法が普及。 Chen 記法より省スペースで読みやすく、 現代の主流に。
ANSI SQL 標準化。 ER 図から SQL DDL への変換が体系化される。 ER 図 → CREATE TABLE 文 の機械的変換が可能に。
ERwin、 ER/Studio、 PowerDesigner などの専用ツールが企業で広く採用。 ER 図のラウンドトリップ(順方向・逆方向同期)が実現。
UML クラス図が ER 図の代替候補として浮上。 ただし DB 専用の表現力では ER 図が依然優位。
MongoDB、 Cassandra、 DynamoDB の登場で「スキーマレス」が流行。 ER 図不要論も。 しかし大規模システムでは結局スキーマ設計が必要、 という揺り戻し。
Mermaid、 PlantUML、 dbdiagram.io でテキストベース ER 図が普及。 git で変更履歴管理、 CI で自動レンダリング。 DevOps 文化との融合。
ER 図実装の細部。
ER 図運用での難題。
業務要件は変わり続ける。 ER 図も常にアップデート。 ツール選定時に「ラウンドトリップ可能性」を確認。
Alembic、 Flyway、 Liquibase でスキーマ変更を git 管理。 本番反映前にステージングで検証。
EXPLAIN ANALYZE で実行計画確認。 SELECT 遅延は (a) インデックス追加、 (b) クエリ書き換え、 (c) パーティション、 (d) 非正規化 の順で対処。
schemaspy、 sqldef、 tbls で DB スキーマから ER 図とドキュメントを自動生成。 README で公開。
参照整合性、 制約、 トリガーで DB レベルで品質確保。 アプリ層だけに頼らない。
ER 図関連のコスト。
| ツール | 料金 | ライセンス |
|---|---|---|
| DrawIO | 無料 | ASL |
| Mermaid | 無料 | MIT |
| dbdiagram.io | 無料 / 9 ドル/月 | クラウド |
| Lucidchart | 8–9 ドル/月 | クラウド |
| ERwin | 3,000 ドル+/年 | 商用 |
| ER/Studio | 2,000 ドル+/年 | 商用 |
中規模システム(50 テーブル程度)の初期 ER 設計:シニアエンジニア 1 名 × 1 ヶ月 ≈ 150 万円。 修正フェーズはさらに同程度。
新人エンジニアに ER 図の読み書きを教えるのに 20〜40 時間。 OJT で覚えさせる方が定着率高い。
ER 図関連のガバナンス。
各エンティティの所有部門を明確化。 個人情報(人)は人事部、 売上(売上)は経理部、 など。 RACI マトリクス。
DAMA 6 次元(正確性・完全性・一貫性・適時性・一意性・妥当性)で各エンティティを評価。
個人情報を含むカラムに「PII」フラグ。 暗号化、 アクセスログ、 匿名化を必須化。
エンティティ間のデータの流れを記録。 Apache Atlas、 OpenLineage で自動化。
DB 構造変更履歴を git で管理。 SOX 法、 J-SOX 対応。
Amazon・楽天規模の EC では、 商品(products)、 注文(orders)、 注文明細(order_items)、 顧客(customers)、 配送先住所(addresses)が中核エンティティ。 多対多はすべて関連テーブルで解消。 1 日数百万件の注文をさばくため、 パーティション・シャーディング前提の設計。
顧客(customers)、 口座(accounts)、 取引(transactions)、 支店(branches)。 transactions は append-only(更新・削除なし、 補正取引で対応)が金融業界のお作法。 ER 図に「監査列」(created_by、 created_at)必須。 BCBS 239(バーゼル委員会のデータガバナンス原則)への対応。
患者(patients)、 診療(encounters)、 処方(prescriptions)、 検査(lab_results)、 医師(doctors)。 HL7 FHIR 標準に準拠した ER 設計。 個人情報の最も厳格な扱いが必要で、 3 省 2 ガイドラインに準拠。
社員(employees)、 部署(departments)、 役職(positions)、 給与(salaries)、 評価(evaluations)。 「社員 ⇄ 部署」が多対多(兼務)。 給与は履歴管理必須(valid_from、 valid_to で時系列)。 SCD Type 2 パターン。
学生(students)、 講義(courses)、 履修(enrollments)、 教員(faculty)。 「学生 ⇄ 講義」の多対多を履修テーブルで解消、 「成績」「履修日」を履修テーブルに付加。 ER 図設計の教科書的例。
都道府県(prefectures)、 指標(indicators)、 観測値(observations)の 3 テーブル構成。 数百種の指標を観測値テーブルの 1 行ずつに正規化することで、 指標追加に強い柔軟な設計に。 国立統計研究所、 RESAS、 e-Stat の内部構造もこの方向。
| 記法 | 発案者 | 年 | エンティティ | 関係 | 多重度 | 現代利用 |
|---|---|---|---|---|---|---|
| Chen 記法 | Peter Chen | 1976 | 四角 | 菱形 | 1, N, M:N | 教育・学術 |
| IE 記法 (Crow's Foot) | James Martin | 1981 | 四角(属性列挙) | 線 | クロウフット | 商用主流 |
| Barker 記法 | Richard Barker | 1985 | 角丸四角 | 線(点線含む) | 破線/実線 | Oracle 文化 |
| IDEF1X | 米国空軍 | 1985 | 四角 | 線 | 記号 | 政府機関 |
| UML クラス図 | Booch/Rumbaugh/Jacobson | 1995 | 四角(区画分け) | 線 | 0..1, 1..* | OOP 統合 |
ER 図設計で頻出する「第 N 正規形」の到達条件と典型的なユースケースを表に整理する。 SSDSE-B-2026 のような分析用データセットでは 3NF が標準だが、 DWH では意図的に 2NF に留めるケースもある。
| 正規形 | 到達条件 | 解消する問題 | 典型用途 |
|---|---|---|---|
| 1NF | 各セルがアトミック値 | 繰り返し項目 | あらゆる RDB の前提 |
| 2NF | 1NF + 部分関数従属の排除 | 複合 PK の冗長 | 業務系 DB の最低ライン |
| 3NF | 2NF + 推移的関数従属の排除 | 更新異常 | OLTP 標準 |
| BCNF | 3NF + すべての決定子が候補キー | 多値依存 | 厳密な業務系 |
| 4NF / 5NF | 多値・結合依存の排除 | 結合損失 | 研究・学術 |
SSDSE-B-2026 (47 都道府県 × 多変量年次データ) を RDB スキーマに落とす際の対応関係を示す。 「年×都道府県」が複合 PK、 指標は属性 (人口・出生数など) となる。
| SSDSE 列 | 論理名 | 型 | PK / FK | 備考 |
|---|---|---|---|---|
| SSDSE-2026 | 年 | SMALLINT | PK (一部) | 2010–2024 等 |
| 都道府県コード | pref_code | CHAR(6) | PK + FK | R01100 など |
| 都道府県名 | pref_name | VARCHAR(8) | 属性 | 正規化なら別表 |
| A1101 | 総人口 | BIGINT | 属性 | 千人単位 |
| A1303 | 65 歳以上人口 | BIGINT | 属性 | 高齢化率算出元 |
A. DB 設計が主なら ER 図、 ソフトウェア設計と統合したいなら UML。 ER 図は「データ」、 UML は「データ+振る舞い」。 用途に応じて。
A. MongoDB、 Cassandra でも「コレクション間の関係」を可視化する価値あり。 ただし正規化原則は緩い。 アクセスパターン中心の設計が重要。
A. DrawIO(GUI)、 Mermaid(テキスト)、 dbdiagram.io(DSL)。 用途で使い分ける。
A. Mermaid / PlantUML をテキストで git 管理 + CI で画像化が最強。 Miro / Lucidchart などのリアルタイム共同編集も選択肢。
A. Yes。 pgAdmin、 MySQL Workbench、 DBeaver、 schemaspy で自動生成。 ただし命名規則が悪い DB だと読みづらい。
A. 業務系なら 3NF が標準。 OLAP / DWH では意図的に非正規化(スタースキーマ)。 用途次第。
A. スキーマ変更 PR と同じタイミング。 Mermaid 化していれば自動。 「文書更新を後回しにしない」が鉄則。
A. 概念モデル(業務寄り)→ 論理モデル(RDB 設計、 正規化済み)→ 物理モデル(特定 DBMS 向け)。 ER 図は概念〜論理を表現する。
A. テーブル名は複数形(users、 orders)、 ER 図上のエンティティ名は単数(User、 Order)が多数派。 流派次第。
A. 『達人に学ぶ DB 設計徹底指南書』(ミック著)、 『SQL アンチパターン』(Karwin 著)、 『リレーショナルデータベース入門』(増永良文著)。
ER 図は単独の作図ではなく、 業務分析 ・正規化 ・物理 DB 設計 ・SQL DDL 生成を結ぶ設計ドキュメントである。 エンティティ ・関係 ・属性 ・カーディナリティを業務要件と紐付けて記述する。
ER 図は「実体 (Entity) ・関係 (Relationship) ・属性で DB スキーマを表現する」設計手法で、 上流の業務要件分析でエンティティを抽出し、 並列の UML クラス図・正規化と組み合わせ、 下流の物理 DB 設計・SQL DDL 生成へと運ぶ。
ER 図の選定・運用での意思決定をツリーで整理。
現状システムに課題があるか?
├─ Yes → 課題はコスト/性能/拡張性/信頼性?
│ ├─ コスト → ROI 試算で ER 図 採用検討
│ ├─ 性能 → ベンチマークで比較
│ ├─ 拡張性 → スケーラビリティ要件を整理
│ └─ 信頼性 → SLA, MTBF を比較
└─ No → 「動いているものは触らない」原則
ワークロードの特性は?
├─ 予測可能・常時稼働 → リザーブド/専有
├─ 変動大・短期 → サーバーレス/スポット
├─ レイテンシ厳しい → エッジ/フォグ
└─ コンプライアンス厳しい → プライベート/オンプレ
症状は?
├─ 完全停止 → ロールバック先行、 原因究明は後
├─ 性能劣化 → メトリクス/ログ/トレースで根因分析
├─ コスト急増 → 利用量分析、 不正アクセス疑い
└─ セキュリティイベント → CSIRT 起動、 隔離
ER 図の本質は、 世界を 3 つの品詞に分けて絵にすることだと捉えると迷わない。 これは前述の「組織図+家系図」の比喩を、 文法の言葉で言い換えたものである。
実データ(SSDSE-B-2026.csv、 cp932、 skiprows=[1])は 564 行 × 112 列。 内訳を三要素に写像すると次のとおり。 これは捏造ではなく実ファイルの形状に一致する。
| CSV 上の実測 | ER 三要素での役割 | 設計上の帰結 |
|---|---|---|
| 行数 564 = 47 × 12 | 「都道府県」47 と「年度」12(2012–2023)の直積がインスタンス | 1 行 = (県, 年) の 1 観測 → 複合主キー候補 |
| 先頭 3 列(SSDSE-B-2026=年, Code, Prefecture) | 識別のためのキー属性 | Code(R01000 等) が県の PK、 年と組んで複合 PK |
| 残り 109 列(A1101 総人口 ほか) | 観測値の測定属性 | ワイドのままか、 縦持ち(EAV)で指標を実体化するかの分岐 |
たとえば 2023 年の総人口列 A1101 は東京都=14,086,000 が最大値(実データ)。 この 1 マスは「実体=都道府県(東京都)、 年度(2023)」×「属性=総人口」の交点にすぎない。 1 マスを主キーで一意に特定できることが、 ER 図がめざす「行の意味の明確化」である。
数式セクションの 4 問法を、 直感側から補う。 「片側から相手を見たとき何本の線が出るか」だけを見る。
冒頭「落とし穴 5 件」を、 原因まで遡って整理する。 表面の症状だけ覚えても再発するので、 「なぜ起きるか」を押さえる。
「1 県に複数の観測値」だけ見て 1:N と決めると、 反対向き(1 観測値は何県に属すか)を検証し忘れる。 両方向を必ず問う。 SSDSE では「1 観測値 = ちょうど 1 県」なので安全に 1:N。 だが「県 ─ 指標」を素朴に結ぶと、 1 県は多指標・1 指標は多県で N:M になり、 連関テーブル(=観測値)が必要になる。 実は SSDSE の 109 指標列は、 この N:M を暗黙に横持ちで潰した状態と読める。
| 症状 | 正規化不足(冗長) | 正規化過剰 |
|---|---|---|
| 典型 | 1 表に 109 列+県名を毎行反復 | 指標ごとに別表、 型ごとに別表へ細断 |
| 害 | 更新異常・矛盾(県名の表記ゆれ) | JOIN 爆発で読み取りが激重 |
| 対処 | 県マスタへ分離(3NF) | 意図的な非正規化・まとめ直し |
原則は 「まず 3NF、 必要なら意図して崩す」。 崩す判断(非正規化)は性能計測の後に行う。 早すぎる非正規化は技術的負債になる。
単独では識別できず、 親に依存する実体を 弱実体と呼ぶ。 自分だけでは主キーを構成できず、 親の PK+自分の部分キーで複合 PK を作る(識別関係)。 SSDSE の「観測値」は、 都道府県と年度がないと意味を持たない典型的な弱実体で、 PK は (Code, 年)。 弱実体を強実体と誤認して代理キー(連番 id)だけ振ると、 (Code, 年) の一意性制約を張り忘れ、 重複行が忍び込む。
id BIGSERIAL PRIMARY KEY だけ付け、 (Code, 年, 指標) の UNIQUE を忘れる → 同じ (県, 年, 指標) が 2 行入っても DB は気づかない。 代理キーを使う場合も 業務上の一意性は UNIQUE 制約で別途保証する。同じ概念が pref_code / prefCode / 都道府県コード / Code と揺れると、 JOIN 条件で人的ミスが多発する。 プロジェクト開始時に snake_case 統一・FK は参照先_id 形式などを文書化し、 SSDSE の日本語列(年度・Code・Prefecture)も論理名へ正規化してから設計に入る。
ER 図は、 ほぼ機械的に表定義へ落とせる。 この 7 つの規則を覚えると設計が速い。
| ER 上の要素 | 変換規則 | SSDSE での適用 |
|---|---|---|
| 強実体 | 1 実体 → 1 表、 主キーはそのまま PK | prefectures(Code PK) |
| 1:1 関連 | どちらか一方に FK(NULL 少ない側に寄せる) | 該当薄い(統合で足りる) |
| 1:N 関連 | 「多」側に「1」側の PK を FK として置く | observations に Code を FK |
| M:N 関連 | 連関表を新設、 両 PK を複合 PK 兼 FK に | observations が (Code, 年, 指標) |
| 多値属性 | 別表に切り出し 1:N 化 | 指標を縦持ち(EAV)化 |
| 複合属性 | 構成要素ごとに列分解 | 住所→市/番地 等(SSDSE では不要) |
| 弱実体 | 親 PK + 部分キーで複合 PK | observations = (Code, 年) |
ER 図の「論理化」は、 実体の各表に正規化を適用する工程そのもの。 テーブルの粒度を決める理論的裏付けが正規形である(本用語集に正規化の独立ページは未整備のため、 ここでは要点をテキストで示す)。
比較表(📊 比較表)に加え、 実務で迷いやすい点を補う。
0..1 / 1..* のように数値範囲で書く。 データだけでなく「振る舞い(メソッド)」も持てるのが ER 図との差。ER 図+正規化は「書き込みの整合性」を最優先する RDB の思想。 NoSQL は逆に アクセスパターン駆動で設計する。
| 観点 | RDB(ER 図・正規化) | NoSQL(例:ドキュメント DB) |
|---|---|---|
| 設計の起点 | 実体と関係(データ構造) | クエリ/画面(アクセスパターン) |
| 冗長 | 正規化で排除 | 埋め込み(embed)で意図的に重複 |
| 結合 | JOIN で実行時に接続 | あらかじめ 1 ドキュメントに同梱 |
| SSDSE の例 | 県・年・観測を 3 表に分離 | 県ドキュメントに年次配列を内包 |
ただし NoSQL でも「どの実体がどう関係するか」を把握する意味で ER 的思考は有効で、 スキーマレス=無設計ではない。 大規模化すると結局スキーマ設計へ回帰する、 というのが歴史セクションで触れた揺り戻しである。