# Logical Design Antipattern Review

> このスキルは、ユーザーが "/logical-design-antipattern-review" を呼び出したとき、またはDBの論理設計・テーブル設計のアンチパターンチェックを依頼したときに使用する。SQLアンチパターン本（Bill Karwin著）の第I部に基づき、論理設計の問題点を洗い出す。

- Skill: `ken3pei/logical-design-antipattern-review` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ken3pei/logical-design-antipattern-review`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ken3pei/logical-design-antipattern-review/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: KEN3pei (https://skillmd.com/u/ken3pei)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/ken3pei/logical-design-antipattern-review

---


# 論理設計アンチパターンレビュー

『SQLアンチパターン Volume 1』第I部（データベース論理設計のアンチパターン）に基づき、
ユーザーが提示したテーブル定義・ER図・スキーマに対してレビューを実施してください。

## input の取得（最初に必ず実行）

以下の優先順位でレビュー対象を特定すること。

### 1. 引数でファイルパスが指定された場合
スキル呼び出し時に引数としてファイルパスが渡された場合（例: `/logical-design-antipattern-review ./schema.sql`）、そのファイルを Read ツールで読み込む。

### 2. 引数がなく、会話にスキーマ・DDL・設計情報が貼り付けられている場合
会話中のテキストをそのままレビュー対象として使用する。

### 3. 引数もなく、会話にも設計情報がない場合
以下を質問してから処理を開始する：
```
レビュー対象を教えてください。以下のいずれかで指定できます：
- ファイルパス（例: ./schema.sql, ./er-diagram.drawio）
- DDL・テーブル定義を直接貼り付け
```

### ファイル種別ごとの読み取り方針

| 拡張子 | 読み取り方法 |
|--------|------------|
| `.sql` | Read ツールでそのまま読み込み、CREATE TABLE / ALTER TABLE 等のDDLを解析する |
| `.drawio` / `.xml` | Read ツールでXMLを読み込み、`<mxCell>` タグのラベル・属性からテーブル名・列名・データ型・制約・リレーションを抽出してER構造を把握する |
| `.md` / `.txt` | Read ツールで読み込み、テーブル定義やER図の説明文からスキーマ情報を読み取る |
| その他 | Read ツールで読み込み、内容からスキーマ情報を最大限に読み取る |

**drawio ファイルの場合の注意点:**
- `swimlane` スタイルのセルがテーブルを表す
- 子セルの `value` 属性が列名・データ型・制約を示す
- エッジ（矢印）がテーブル間のリレーションを示す
- これらを解釈してER構造として理解した上でレビューを行う

## レビュー手順

1. 上記 input の取得手順に従って対象を特定し読み込む
2. 以下の8つのアンチパターンチェックリストに従って順番に確認する
3. 該当するアンチパターンを発見した場合は問題点と改善策を日本語で明確に報告する
4. 問題がない場合は「問題なし」と明記する

---

## チェックリスト

### AP-1: ジェイウォーク（信号無視）
**チェック内容:** VARCHAR列にカンマ区切りなどで複数値（ID等）を格納していないか

**問題点:**
- インデックスが使用できずパフォーマンスが悪化する
- 正規表現でのパターンマッチが必要となりクエリが複雑化する
- 集約クエリ（COUNT/SUM/AVG）が困難になる
- バリデーション（参照整合性）が機能しない
- リストの長さに上限が生まれる

**確認観点:**
- `VARCHAR` 列に複数のIDや値をカンマ区切りで格納していないか
- `account_id VARCHAR(100)` のようにIDを文字列列に格納していないか
- `REGEXP` や `LIKE '%,%'` を使ったクエリが存在しないか

**解決策:** 多対多の関連には交差テーブル（中間テーブル）を作成する

---

### AP-2: ナイーブツリー（素朴な木）
**チェック内容:** 階層構造を `parent_id` のみで表現していないか

**問題点:**
- 全子孫取得には JOIN をツリーの深さ分だけ繰り返す必要がある
- 深さが可変の場合、クエリを動的に生成しなければならない
- COUNT/SUM などの集約が困難になる

**確認観点:**
- 同じテーブルの自己参照として `parent_id` のみが存在しないか
- 深さが無制限の階層データを扱うテーブルで `parent_id` だけが頼りになっていないか

**解決策:** 以下の代替ツリーモデルを検討する
- 再帰CTE（WITH RECURSIVE）
- 経路列挙（Path Enumeration）: `/1/2/3/` のような文字列でパスを格納
- 入れ子集合（Nested Set）: lft/rgt値でツリーを表現
- 閉包テーブル（Closure Table）: 全祖先・子孫ペアを格納する別テーブル

---

### AP-3: IDリクワイアド（とりあえずID）
**チェック内容:** 全テーブルに機械的に `id SERIAL PRIMARY KEY` を付けていないか

**問題点:**
- 交差テーブル等で複合キーが必要なのに単一の `id` 列を使うと重複行を許可してしまう
- 自然キー・複合キーが使えるのに冗長な疑似キーが増える
- キーの意味がわかりにくくなる（`id` が何を識別しているか不明確）

**確認観点:**
- 交差テーブル（多対多）に `id` 列だけが主キーで、`UNIQUE(col1, col2)` がないか
- 本来自然キーが使える列（メールアドレス等）にも `id` を追加していないか
- テーブル名やコンテキストから `id` の意味が読み取れないか

**解決策:** 状況に応じて適切に調整する
- 交差テーブルは複合主キーを使う
- 自然キーが一意であれば主キーとして使う
- 列名を `bug_id`, `account_id` のように意味のある名前にする

---

### AP-4: キーレスエントリ（外部キー嫌い）
**チェック内容:** 外部キー制約が宣言されていない関連が存在しないか

**問題点:**
- アプリケーション側で参照整合性を保証するコードが必要になる
- 「完璧なコード」を前提とした脆弱な設計になる
- 孤立した参照（存在しない親を参照する子行）が生まれうる
- バグの発見が遅れる（データ破損が起きてから気づく）

**確認観点:**
- テーブル間に論理的な関連があるのに `FOREIGN KEY` 制約が宣言されていないか
- `ON DELETE CASCADE` / `ON DELETE SET NULL` の適切な設定がされているか

**解決策:** 外部キー制約を宣言し、参照整合性をDBに委ねる

---

### AP-5: EAV（エンティティ・アトリビュート・バリュー）
**チェック内容:** 属性名を行として格納する汎用属性テーブル設計を使っていないか

**問題点:**
- データ型の制約が効かない（数値も日付も全てVARCHARになる）
- NOT NULL制約が機能しない
- 行を再構築するためのPIVOT処理が必要になりクエリが複雑化する
- 外部キー制約で参照整合性が保てない
- 特定エンティティの全属性をSELECTするためにJOINが爆発する

**確認観点:**
- `entity_id`, `attribute_name`, `value` のような3列構成のテーブルがないか
- 属性名を文字列として格納しているテーブルがないか
- 「オープンスキーマ」「スキーマレス」「名前/値ペア」と呼ばれる設計がないか

**解決策:** サブタイプのモデリングを行う
- シングルテーブル継承: 全サブタイプを1テーブルに（NULLが多くなる）
- 具象テーブル継承: サブタイプごとに独立したテーブル
- クラステーブル継承: 共通属性を基底テーブルに、固有属性を各サブタイプテーブルに
- 半構造化データ: JSON/XMLカラムを使う（最終手段）

---

### AP-6: ポリモーフィック関連
**チェック内容:** 外部キーが複数の親テーブルのいずれかを参照する「二重目的の外部キー」を使っていないか

**問題点:**
- SQL の FOREIGN KEY 制約は複数テーブルへの参照を表現できない
- `issue_type VARCHAR` + `issue_id BIGINT` のような組み合わせは参照整合性が保てない
- JOIN するたびに型の分岐処理が必要になる

**確認観点:**
- `target_type` や `parent_type` のような「どのテーブルを指すか」を示す列がないか
- 外部キーが `REFERENCES` なしに定義されている関連IDカラムがないか
- `Comments.issue_type IN ('Bug', 'FeatureRequest')` のような型識別カラムがないか

**解決策:**
- 参照を逆向きにする（子側に外部キーを持つのではなく親側に従属テーブルを作る）
- 共通の基底テーブルを作成して全親テーブルがそれを参照する設計にする

---

### AP-7: マルチカラムアトリビュート（複数列属性）
**チェック内容:** 同じ種類のデータを `tag1`, `tag2`, `tag3` のように複数列で表現していないか

**問題点:**
- 特定の値を検索するとき全列を検索しなければならない
- 列数の上限が値の上限になる
- 未使用列にNULLが増える
- 列の追加にはALTER TABLEが必要

**確認観点:**
- 同じ種類のデータを `_1`, `_2`, `_3` のような連番サフィックスで複数列定義していないか
- `phone1`, `phone2`, `phone3` や `tag1`, `tag2`, `tag3` のような列がないか

**解決策:** 従属テーブルを作成する（1対多の関連として正規化する）

---

### AP-8: メタデータトリブル（メタデータ大増殖）
**チェック内容:** データ増加対策として年・月・カテゴリ等でテーブルや列を分割していないか

**問題点:**
- テーブルや列がトリブルのように制御不能に増殖する
- 全データを対象にしたクエリでUNION ALLが必要になる
- データ整合性の管理が困難になる
- メタデータ（テーブル名・列名）にデータが混入する（例: `sales_2023`, `sales_2024`）

**確認観点:**
- 年・月・カテゴリ等で名前が変わるテーブルが複数存在しないか（例: `log_2023`, `log_2024`）
- `revenue2022`, `revenue2023` のように年が列名に入っていないか
- テーブル名や列名がデータとして機能していないか

**解決策:**
- 水平パーティショニング（DB機能のパーティショニング）を使用する
- 垂直パーティショニング（列の分割）を検討する
- 年・月は列の値として格納し、テーブル名・列名に埋め込まない

---

## 出力形式

```
## 論理設計アンチパターンレビュー結果

### 発見されたアンチパターン

#### AP-X: [アンチパターン名]
- **箇所:** （問題のあるテーブル名・列名）
- **問題点:** （具体的な問題の説明）
- **改善策:** （推奨する設計変更）

### 問題なし
- AP-X: [アンチパターン名] — 該当なし

### 総評
（全体的な評価と優先度の高い改善点のサマリー）
```

