🎯 STEP 12: 正規化総合演習
実践的な演習問題で正規化の技術を完全にマスターしよう!
- 実践的な正規化プロジェクトに挑戦
- 非正規形から第3正規形まで段階的に正規化
- 複雑なビジネスルールをデータベース設計に落とし込む
- 実務で使えるテーブル設計スキルを習得
学習時間の目安: 5時間 | 前提知識: STEP 7-11(正規化の基礎から応用)を理解していること
🎯 1. このステップの進め方
STEP 11からの続き:正規化編の総仕上げ
STEP 7〜11では、正規化の理論と各正規形(1NF、2NF、3NF、BCNF)を学びました。このSTEP 12では、学んだ知識を総合的に活用して、実務に近いシナリオで正規化に取り組みます。
4つの総合演習を通じて、正規化のスキルを実践レベルに引き上げましょう。
| 演習 | テーマ | 目安時間 | 難易度 |
|---|---|---|---|
| 演習1 | 学生成績管理システム | 90分 | 中級 |
| 演習2 | Excelの販売実績表をDB化 | 120分 | 上級 |
| 演習3 | 集計テーブルの非正規化判断 | 90分 | 中級 |
| 演習4 | 病院予約システムの正規化 | 60分 | 上級 |
- まず自分で考えて設計してみる(解答を見る前に)
- ER図を紙に書いてみる(視覚化が重要)
- 各テーブルの主キー、外部キーを明確にする
- 解答と比較して、なぜその設計になったかを理解する
- 時間をかけてじっくり取り組む(急がない)
📝 演習1:学生成績管理システム(90分)
学生成績管理システムのデータベース設計
ある大学の成績管理システムを設計します。以下の情報を管理する必要があります:
- 学生情報(学生ID、氏名、学科、入学年度)
- 科目情報(科目コード、科目名、単位数、担当教員)
- 教員情報(教員ID、教員名、所属学部)
- 履修情報(学生がどの科目を履修し、どんな成績を取ったか)
- 学科情報(学科コード、学科名、所属学部)
- 1人の学生は1つの学科に所属
- 1つの科目は1人の教員が担当(複数教員での担当はない)
- 1人の学生は複数の科目を履修可能
- 1つの科目を複数の学生が履修可能
- 成績は「優」「良」「可」「不可」の4段階
- 上記のExcel管理の問題点を3つ以上指摘してください
- 第1正規形、第2正規形、第3正規形の順に段階的に正規化してください
- 最終的なテーブル設計(テーブル名、カラム名、主キー、外部キー)を示してください
- 主要なテーブルについてCREATE TABLE文を書いてください
- 「学生ID: S001 の全履修科目と成績を取得するSQL」を書いてください
1. Excelでの管理の問題点:
- 第1正規形違反:履修科目、科目コード、担当教員、教員所属、成績に繰り返し項目がある(カンマ区切り)
- データの重複:山田太郎と鈴木一郎の学科情報「情報工学、工学部」が重複している
- 更新異常:「情報工学科」の名前を変更する場合、全ての学生レコードを更新する必要がある
- 挿入異常:新しい学科を登録したいが、所属学生がいないと登録できない
- 削除異常:佐藤花子が退学すると、「電気工学科」の情報も失われる可能性がある
- 推移的関数従属性:学生ID → 学科 → 学部という推移的な依存関係がある
2. 段階的な正規化:
繰り返し項目を排除します。
部分関数従属性を排除します。主キーは(学生ID, 科目コード)です。
推移的関数従属性を排除します。
3. 最終的なテーブル設計:
- faculties(学部):学部コード(PK), 学部名
- departments(学科):学科コード(PK), 学科名, 学部コード(FK)
- students(学生):学生ID(PK), 学生名, 学科コード(FK), 入学年度
- teachers(教員):教員ID(PK), 教員名, 学部コード(FK)
- courses(科目):科目コード(PK), 科目名, 単位数, 教員ID(FK)
- enrollments(履修):履修ID(PK), 学生ID(FK), 科目コード(FK), 成績, 履修年度
4. CREATE TABLE文:
5. 学生ID: S001の全履修科目と成績を取得するSQL:
実行結果:
✅ 設計のポイント:
- 学部 → 学科 → 学生 という階層構造を明確に分離
- 教員は学部に所属(科目ではなく)
- 履修テーブルに履修IDを追加し、同じ科目の再履修に対応
- UNIQUE制約で「同一年度の同じ科目の重複履修」を防止
- すべてのテーブルが第3正規形を満たしている
📝 演習2:Excelの販売実績表をDB化(120分)
営業部のExcel販売実績表をデータベース化
ある会社の営業部では、Excelで販売実績を管理しています。しかし、データの重複や入力ミスが多発しており、データベース化することになりました。
現在の問題点:
- 商品情報がカンマ区切りで複数入っている
- 営業担当の所属支店情報が重複している
- 顧客情報(顧客名、住所)が重複している
- 支店情報(支店名、住所)が重複している
- 商品の単価が売上ごとに記録されており、価格改定時の整合性が取れない
- 売上データは後から変更されない(確定したデータ)
- 商品マスタは別途管理し、現在の価格を保持
- 売上時の単価は、その時点の価格を記録(価格改定の影響を受けない)
- 営業担当者の異動(支店間の移動)にも対応できること
- 月次・年次の売上集計レポートを高速に作成したい
- 上記のExcel管理の全ての問題点を指摘してください
- 第3正規形までテーブルを設計してください
- 月次売上集計レポート用に非正規化テーブルを設計し、その理由を説明してください
- 主要なテーブルのCREATE TABLE文を書いてください
- 「2025年1月の営業担当者別売上合計を取得するSQL」を2パターン書いてください:
(a) 正規化されたテーブルから集計
(b) 非正規化された集計テーブルから取得
1. Excelでの管理の問題点:
- 第1正規形違反:商品名、数量、単価が繰り返し項目(カンマ区切り)
- 推移的関数従属性:売上ID → 営業担当 → 所属支店、支店住所
- 推移的関数従属性:売上ID → 顧客名 → 顧客住所
- データの重複:「山田太郎、東京支店、東京都…」が複数行に重複
- データの重複:「A商事、東京都…」が複数行に重複
- 更新異常:営業担当が異動した場合、過去の全売上データを更新してしまうと履歴が失われる
- 一貫性の問題:商品単価が売上ごとに記録され、商品マスタとの整合性が保証されない
2. 第3正規形のテーブル設計:
3. 非正規化テーブルの設計と理由:
非正規化の理由:
- ✅ 読み取り頻度が高い:月次レポートは経営会議などで頻繁に参照される
- ✅ 更新頻度は低い:月次集計は月1回のバッチ処理で更新
- ✅ JOIN不要:複雑なJOINと集計が不要になり、レポート表示が超高速化
- ✅ 過去データの保持:営業担当の異動があっても、その月の実績として記録される
- ⚠️ 元の正規化されたテーブルは維持し、真実の情報源として保持
- ⚠️ このテーブルは月次バッチで sales_headers と sales_details から集計して生成
4. CREATE TABLE文:
5. 2025年1月の営業担当者別売上合計を取得するSQL:
(a) 正規化されたテーブルから集計:
(b) 非正規化された集計テーブルから取得:
実行結果(どちらも同じ):
✅ 設計のポイント:
- 売上時単価を記録:商品の価格改定があっても、過去の売上データは変わらない
- 売上時の支店を記録:営業担当が異動しても、その売上がどの支店の実績か明確
- ヘッダー・明細分離:1回の売上で複数商品に対応(1対多の関係)
- 戦略的非正規化:頻繁に参照されるレポート用に集計テーブルを用意
- インデックス設計:日付検索、営業担当検索を高速化
- データの整合性:元の正規化テーブルを真実の情報源として維持
📝 演習3:集計テーブルの非正規化判断(90分)
パフォーマンス最適化のための非正規化判断
あるECサイトの管理画面では、ダッシュボードに以下の情報を表示しています:
- 本日の売上金額
- 今月の売上金額
- 商品カテゴリ別の売上トップ5
- 都道府県別の売上分布
現在、これらの情報は毎回リアルタイムで集計していますが、アクセスが集中すると画面表示に3秒以上かかる問題が発生しています。
データ規模:
- 注文:1日あたり約5,000件、総計200万件
- 注文明細:1日あたり約10,000行、総計400万行
- 商品:10,000件
- 顧客:50万人
- 現在のリアルタイム集計の問題点を分析してください
- 非正規化テーブル(集計テーブル)を設計してください:
– 日次売上集計テーブル
– カテゴリ別日次集計テーブル
– 都道府県別日次集計テーブル - 各集計テーブルの更新タイミングと方法を提案してください
- 集計テーブルを使った場合のメリットとデメリットを説明してください
- 「今月の売上金額を取得するSQL」を2パターン書いてください:
(a) 元テーブルから集計
(b) 集計テーブルから取得
1. リアルタイム集計の問題点:
- パフォーマンス問題:400万行の order_details をJOINして集計するため、非常に時間がかかる
- データベース負荷:ダッシュボードのアクセスが集中すると、データベースサーバーの負荷が高まる
- ユーザー体験の悪化:画面表示に3秒以上かかり、ユーザーがストレスを感じる
- スケーラビリティの問題:データ量が増えるほど、さらに遅くなる
- 同じ集計の繰り返し:多くのユーザーが同じ期間の集計を見るのに、毎回計算している
2. 非正規化テーブルの設計:
3. 更新タイミングと方法:
戦略A:日次バッチ処理(推奨)
- タイミング:毎日深夜2時(アクセスが少ない時間帯)
- 処理内容:前日分のデータを集計して各テーブルに INSERT
- メリット:データベース負荷を分散、確実にデータを集計できる
- デメリット:当日分はリアルタイム集計が必要
戦略B:リアルタイム更新(注文確定時)
- タイミング:注文が確定するたびにトリガーで更新
- 処理内容:該当する集計テーブルの値を UPDATE(加算)
- メリット:常に最新のデータが集計テーブルに反映される
- デメリット:トランザクション処理が重くなる、複雑なロジックが必要
推奨アプローチ:ハイブリッド方式
- 過去データ:日次バッチで集計テーブルに格納
- 当日データ:リアルタイム集計(ただしキャッシュを活用)
- 表示時:集計テーブル(過去)+ リアルタイム集計(当日)を合算
4. メリットとデメリット:
| メリット | デメリット |
|---|---|
|
|
5. 今月の売上金額を取得するSQL:
(a) 元テーブルから集計(遅い):
(b) 集計テーブルから取得(超高速):
✅ 設計のポイント:
- 目的に応じた粒度:日次、月次など、用途に応じた集計粒度を選択
- ハイブリッド方式:過去データは集計テーブル、当日はリアルタイム集計
- インデックス設計:集計日でのフィルタリングを高速化
- 監視とアラート:バッチ処理の失敗を検知する仕組み
- 元データの保持:正規化されたテーブルは真実の情報源として維持
- 段階的な導入:まず最も負荷の高い集計から非正規化
📝 演習4:病院予約システムの正規化(60分)
病院予約システムの総合設計
総合病院の予約管理システムを設計します。以下の機能が必要です:
- 患者が医師を指定して診察予約
- 医師は複数の診療科を担当可能
- 予約の変更・キャンセル履歴の管理
- 診察結果と処方薬の記録
- 1つの予約枠は30分
- 1人の患者は同じ時間に複数予約できない
- 1人の医師は同じ時間に複数の患者を診察できない
- 医師は曜日・時間帯ごとに診察可能な診療科が異なる
- 予約は変更・キャンセル可能だが、履歴を残す
- 必要な全てのテーブルを第3正規形で設計してください
- 各テーブルの主キー、外部キーを明確にしてください
- ER図を描いてください(または文章で関係性を説明)
- 予約の変更・キャンセル履歴をどう管理するか設計してください
- 「2025年1月15日 14:00-14:30 の予約可能な医師一覧を取得するSQL」を書いてください
1. テーブル設計(第3正規形):
2. 各テーブルの詳細設計(CREATE TABLE文):
3. ER図(関係性の説明):
- 患者 ↔ 予約:1対多(1人の患者は複数の予約を持つ)
- 医師 ↔ 予約:1対多(1人の医師は複数の予約を持つ)
- 診療科 ↔ 予約:1対多(1つの診療科で複数の予約)
- 医師 ↔ 診療科:多対多(1人の医師が複数の診療科を担当、1つの診療科に複数の医師)
- 予約 ↔ 予約履歴:1対多(1つの予約に複数の履歴)
- 予約 ↔ 診察記録:1対1(1つの予約に1つの診察記録)
- 診察記録 ↔ 処方薬:1対多(1つの診察で複数の薬を処方)
- 薬品マスタ ↔ 処方薬:1対多(1つの薬品が複数の処方に使われる)
4. 予約の変更・キャンセル履歴の管理:
設計方針:
- appointments テーブル:現在の予約状態のみを保持
- appointment_history テーブル:全ての変更履歴を記録
- トリガーまたはアプリケーション側:予約の作成・変更・キャンセル時に自動的に履歴を記録
履歴記録のタイミング:
- 予約作成時:change_type = ‘created’
- 予約変更時:change_type = ‘modified’, 旧日時と新日時を記録
- 予約キャンセル時:change_type = ‘cancelled’, appointments.status = ‘cancelled’
5. 2025年1月15日 14:00-14:30 の予約可能な医師一覧:
実行結果例:
✅ 設計のポイント:
- ダブルブッキング防止:UNIQUE制約で同じ患者・医師の同時間予約を防止
- 医師の診療時間管理:doctor_departments で曜日・時間帯ごとの診療科を管理
- 履歴の完全性:全ての変更を appointment_history に記録
- 柔軟な検索:インデックスで高速な空き枠検索
- 診察記録との連携:予約から診察記録、処方へのトレーサビリティ
- データの整合性:外部キー制約で参照整合性を保証
🎓 総合演習のまとめ
- 実践的な正規化技術:非正規形から第3正規形まで段階的に設計
- ビジネスルールの反映:現実の業務要件をテーブル設計に落とし込む
- 戦略的な非正規化:パフォーマンス要件に応じた適切な判断
- 履歴管理の設計:変更履歴を適切に保持する方法
- 制約の活用:UNIQUE制約、外部キー制約でデータの整合性を保証
- インデックス設計:クエリパフォーマンスを考慮した設計
- 段階的な設計:いきなり完璧を目指さず、第1正規形→第2正規形→第3正規形と段階的に進める
- ビジネスルールの理解:業務要件を深く理解してから設計する
- パフォーマンスとのバランス:正規化と非正規化を適切に使い分ける
- ドキュメント化:設計判断の理由を必ず記録する
- レビューの実施:チームメンバーと設計をレビューし、問題点を早期発見
Part 2(正規化)を完了しました!おめでとうございます🎉
次のPart 3では、インデックス設計戦略、データウェアハウス設計、NoSQLなど、より高度で実践的なトピックを学びます。
これまで学んだ正規化の知識は、Part 3でも活かされます。しっかり復習してから次のステップに進みましょう!
❓ よくある質問
Q1: 演習が難しく感じます。どこから手を付ければいいですか?
まず「非正規形の問題点を見つける」ことから始めましょう。繰り返し項目(カンマ区切りのデータ)や、同じデータの重複を探します。問題点が分かれば、どう分割すべきかも見えてきます。紙に書きながら考えると理解が深まります。
Q2: 正規化とSQL作成、どちらを先に覚えるべきですか?
正規化を先に覚えることをお勧めします。正しいテーブル設計ができていれば、SQLは自然と書けるようになります。逆に、正規化されていないテーブルにSQLを書くと、複雑で非効率なクエリになってしまいます。
Q3: 実務では、どの程度の時間をかけてテーブル設計しますか?
プロジェクトの規模によりますが、設計には十分な時間をかけるべきです。小規模なシステムでも1〜2日、中規模なら1〜2週間はテーブル設計に使います。設計を急ぐと、後から大きな修正が必要になることが多いです。
Q4: 非正規化するかどうか、どう判断すればいいですか?
「まず正規化、問題があれば非正規化」が基本です。具体的には、(1)パフォーマンス問題が実際に発生している、(2)インデックスやキャッシュで解決できない、(3)読み取り頻度が高く更新頻度が低い、これらの条件を満たす場合に非正規化を検討します。
Q5: 履歴管理は全てのテーブルに必要ですか?
いいえ、ビジネス要件によります。監査が必要なデータ(金銭に関わるもの、法的に履歴が必要なもの)や、変更の追跡が業務上重要なデータには履歴管理が必要です。全てのテーブルに履歴管理を入れると、複雑さとストレージコストが増加します。
Q6: 演習の解答と異なる設計でも正解ですか?
はい、正解は1つではありません。正規化のルールを満たし、ビジネス要件に対応できていれば、異なる設計でも正解です。大切なのは「なぜその設計にしたか」を説明できることです。解答例は参考の1つとして捉えてください。
学習メモ
データベース設計・データモデリング - Step 12