第5章 正規化して業務データベースを完成させる
🔒 研修参加者向け
この章は研修参加者向けです
この章では、問題のある出荷予定表を第1~第3正規形へ整理し、完成版データベースを構築します。
研修参加者の方は、講師から案内されたパスワードを入力してください。
この教材を利用した研修については、お問い合わせください。
この章のゴール
Section titled “この章のゴール”第4章では、shipment_schedule_rowsを商品や取引先と結合し、
出荷予定ケース数を集計しました。
この中間テーブルは検索には使えますが、同じ出荷予定の日付、取引先、状態、
更新日時が明細行ごとに繰り返されています。
この章では、重複によって起きる問題を確認し、 第1正規形から第3正規形まで段階的に整理します。
この章を終えると、次のことができるようになります。
- データの更新、追加、削除で起きる問題を見つける
- 第1正規形、第2正規形、第3正規形の目的を説明する
- 一つの出荷予定と複数の明細を別のテーブルへ分ける
- 複合主キーと、列が何に依存しているかを確認する
- 取引先、出荷先、商品分類、容器種別をマスターとして分ける
- 正規化したテーブルをMySQLへ構築する
- 正規化の前後で件数と集計結果が一致することを確認する
180分のうち、約80分を正規化の説明と段階演習、約70分をMySQLでの構築、 約30分を確認と振り返りに使います。
使用するデータとSQLファイル
Section titled “使用するデータとSQLファイル”この章は、第4章まで使用したai_database_trainingを引き続き使用します。
次のテーブルとデータがあることを確認してください。
product_categories、package_types、customers、productsがあるshipment_schedule_rowsに7明細が登録されている- 商品コード
PRD001からPRD004が登録されている
確認には、次のSQLを使います。
USE ai_database_training;
SELECT COUNT(*) AS shipment_row_countFROM shipment_schedule_rows;結果が7でない場合は、第4章の準備用SQLを確認してください。
本章では、次のファイルを上から順に使用します。
- 05-normalization-source.csv — 正規化前の一覧
- 05-create-normalized-tables.sql — 完成版の3テーブルを追加
- 05-insert-normalized-data.sql — 正規化後のデータを登録
- 05-verify-normalization.sql — 件数と集計結果を確認
CSVの説明欄には、缶、カン、PETという表記の違いを意図的に含めています。
これは、一覧の各行へ名称や説明を直接書くと、表記ゆれが残りやすいことを確認するためです。
正規化後は、商品コードから商品と容器種別のマスターを参照します。
実在する業務データベースでは実行しないでください。
この章ではテーブルを追加し、複数行のデータを登録します。 講師が指定した研修用MySQLと、架空のサンプルデータだけを使用してください。
正規化は「同じ事実の保存場所を決める」こと
Section titled “正規化は「同じ事実の保存場所を決める」こと”正規化とは、データの重複や依存関係を確認し、 追加、更新、削除を行っても矛盾が起きにくいテーブル構造へ整理することです。
例えば、出荷先の住所を出荷予定の各行へ書いておくと、 住所を訂正するときに、同じ出荷先を使う行をすべて直す必要があります。 そこで「出荷先」専用のテーブルを作り、名称と住所を一度だけ保存します。 出荷予定側には、その出荷先を指すIDを残します。
ここでいう「分ける」は、列の並び替えや、同じ表の途中に空白を入れることではありません。
別のテーブルを作り、そこへ値を移し、共通の番号やIDで対応を残すことです。
後半では、その別テーブルをMySQLのCREATE TABLEで実際に作ります。
テーブル数を増やすこと自体が目的ではありません。 「出荷予定の事実」「明細の事実」「出荷先の事実」の保存場所を決めるために分けます。
重複した一覧で起きる三つの問題
Section titled “重複した一覧で起きる三つの問題”第2章で確認した正規化前の一覧には、商品1、商品2という列グループがありました。 さらに、取引先名、出荷先、商品説明などが出荷予定ごとに繰り返されています。
この状態では、主に三種類の問題が起きます。
| 問題 | 出荷予定表での例 | 業務への影響 |
|---|---|---|
| 更新時の問題 | 同じ商品名を一行だけ修正した | 新旧の商品名が混在する |
| 追加時の問題 | 出荷予定がない新商品を登録したい | 商品を置く行がない |
| 削除時の問題 | 最後の出荷予定を削除した | 取引先や商品の情報まで失う |
このような問題を更新異常、挿入異常、削除異常と呼ぶことがあります。 用語を暗記するより、「一つの操作で、別の事実まで壊れないか」を確認してください。
同じ出荷予定の中で値が食い違う
Section titled “同じ出荷予定の中で値が食い違う”第4章の中間テーブルには、出荷予定SP20261120-001が2行あります。
ここでは問題に関係する列だけを抜き出します。
| 出荷予定番号 | 明細番号 | 状態 | 商品コード | ケース数 |
|---|---|---|---|---|
| SP20261120-001 | 1 | CONFIRMED | PRD001 | 12 |
| SP20261120-001 | 2 | CONFIRMED | PRD003 | 20 |
1行目の状態だけをSHIPPEDへ変更すると、同じ出荷予定の中に
「出荷済み」と「確定済み」が混在します。
本来は一つの出荷予定全体についての状態なのに、保存場所が2行に分かれているためです。
以降は、こうした問題を一つずつ減らしていきます。
図5-1で追いかけるデータ
Section titled “図5-1で追いかけるデータ”図5-1(a)~(c)では、配布CSVから出荷予定2件・商品明細3件を抜き出します。
商品コードPRD001は「みなとソーダ」、PRD003は「みなと天然水」です。
図を読みやすくするため、出荷予定番号を次のように短縮して表示します。 実際のCSVやSQLでは、右欄の正式な番号を使用します。
| 図中の予定番号 | 実際の出荷予定番号 |
|---|---|
| 19-001 | SP20261119-001 |
| 20-001 | SP20261120-001 |
各図では、その段階で注目する列だけを表示します。 (a)は商品欄、(b)は出荷予定日、(c)は出荷先の情報に注目します。 図に表示しない列が、データから消えるわけではありません。
出荷先の補足データ
第4章の中間テーブルには出荷先の詳細がないため、本章で補います。 CSVの「取引先A・DST001・東配送センター」に、説明用の出荷先ID
1と、 架空の住所「研修用県みなと市東1-1」を対応させます。 図では住所を「東1-1」と短縮します。IDと住所は完成版サンプルと同じ値であり、 第4章の表から正規化だけで新しい情報を作り出すわけではありません。
第1正規形:商品1・商品2の欄を、商品ごとの行に直す
Section titled “第1正規形:商品1・商品2の欄を、商品ごとの行に直す”最初は、商品が増えるたびに「商品3」「商品3数量」のような列を増やす表です。
図5-1(a)では、20-001の商品欄を縦に並べ直し、二つの明細行にします。
分割前後で、18ケース、12ケース、20ケースという数量は変わりません。 商品が増えたら、新しい商品欄ではなく新しい明細行を追加できます。 この例では、商品が指定されていない空欄から明細行は作りません。
第1正規形では、一つのセルに複数の値を詰め込まず、 商品1・商品2のような繰り返し項目を整理します。 例えば「PRD001、PRD003」を一つのセルへ入れても、商品ごとの行にしたことにはなりません。
第4章のshipment_schedule_rowsは、このように一つの商品明細を一行にした表です。
「予定番号+明細番号」で一行を見分ける
Section titled “「予定番号+明細番号」で一行を見分ける”図の20-001には明細が2行あるので、予定番号だけでは一行を特定できません。
一方、明細番号1は19-001にも20-001にもあるため、
明細番号だけでも特定できません。
- 予定番号
20-001だけ → 2行が該当する - 明細番号
1だけ → 2行が該当する - 予定番号
20-001と明細番号2→ PRD003・20ケースの1行に決まる
そこで、この二列の組み合わせを主キーにします。 複数列を組み合わせた主キーを複合主キーと呼びます。
第2正規形:出荷予定の情報を、明細とは別のテーブルへ移す
Section titled “第2正規形:出荷予定の情報を、明細とは別のテーブルへ移す”一商品一明細行に直しても、同じ予定の日付や状態は明細ごとに繰り返されています。 図5-1(b)では、出荷予定日の列も表示して確認します。
20-001の出荷予定日は、どちらの明細でも2026-11-20です。
仮に出荷予定日を変更するなら、今の表では2行とも直さなければなりません。
これは、出荷予定日は「商品明細の情報」ではなく「出荷予定全体の情報」だからです。
別テーブルを作り、出荷予定を一件一行で保存する
Section titled “別テーブルを作り、出荷予定を一件一行で保存する”ここで、表を次の二つの別テーブルに分けます。
- 出荷予定テーブルを作る。 予定番号と出荷予定日を移し、同じ予定は一行にまとめる
- 出荷予定明細テーブルには商品と数量を残す。 3行の明細は減らさない
- 予定番号を両方に残す。 どの出荷予定に属する明細か、後から分かるようにする
分けた後で20-001の日付を直すなら、出荷予定テーブルの一行だけを直します。
明細側には日付の列がないため、二つの明細で日付が食い違うことがなくなります。
状態、便、更新日時など、出荷予定全体で一つに決まる値も同じ側へ移します。
出荷予定全体の情報をまとめた部分を、ヘッダーと呼ぶこともあります。 ヘッダーはテーブルの見出し行ではなく、ここでは「出荷予定を一件一行で管理するテーブル」です。
「複合主キーの一部だけで決まる」とは
Section titled “「複合主キーの一部だけで決まる」とは”ここまでの操作を、キーの言葉で説明します。
| 確認したい値 | 具体的な問い | 必要なキー |
|---|---|---|
| 出荷予定日 | 20-001の出荷予定日は? | 予定番号だけで2026-11-20に決まる |
| 商品・ケース数 | 20-001の商品と数量は? | 明細番号もないと12ケースか20ケースか決まらない |
主キーは「予定番号+明細番号」ですが、出荷予定日を知るために明細番号は不要です。 このように、複合主キーの一部だけで値が決まる関係を部分関数従属と呼びます。
第2正規形は、第1正規形を満たした上で、このような関係を解消した形です。 本例では、予定番号だけで決まる日付や状態を、予定番号をキーとする別の出荷予定テーブルへ移す ことで整理しました。商品と数量は、予定番号と明細番号で識別する明細テーブルに残します。
同じ値がたまたま並んでいるから分けるのではありません。 「同じ出荷予定なら、明細が増えても日付は共通」という業務ルールを基に判断します。
第3正規形:出荷先の名前と住所を、出荷先専用のテーブルへ移す
Section titled “第3正規形:出荷先の名前と住所を、出荷先専用のテーブルへ移す”第2正規形への整理で、出荷予定は一件一行になりました。 しかし、その出荷予定テーブルに出荷先名と住所も保存すると、 別の出荷予定の間で同じ名前と住所が繰り返されます。
19-001と20-001は、どちらも出荷先ID 1の東配送センターへ届けます。
図5-1(c)の上段では、同じ「東配送センター・東1-1」が2行にあります。
別の出荷先テーブルを作り、名前と住所の保存先を一か所にする
Section titled “別の出荷先テーブルを作り、名前と住所の保存先を一か所にする”今度は、出荷予定テーブルから「出荷先そのものの情報」を取り出します。
- 出荷先テーブルを作る。 出荷先ID、出荷先名、住所を移す
- 同じ出荷先は一行にまとめる。 東配送センターの名前と住所は一度だけ保存する
- 出荷予定には出荷先IDを残す。 名前と住所は出荷先テーブルを参照して取得する
住所に誤記があったときは、出荷先テーブルのID 1の一行だけを訂正します。
二つの出荷予定は同じIDを参照するので、名称や住所をそれぞれ直す必要はありません。
出荷予定がまだない新しい出荷先も、出荷先テーブルへ単独で登録できます。
この例は、出荷先の現在の名称・住所を参照する設計です。 「出荷当時の住所を保存する」ことが業務要件なら、別途、履歴を残す設計が必要です。 正規化を理由に、保存すべき履歴まで消すわけではありません。
「主キー以外を経由して決まる」とは
Section titled “「主キー以外を経由して決まる」とは”図5-1(c)の分割前では、次の順に情報が決まります。
予定番号 20-001 → 出荷先ID 1 → 東配送センター・研修用県みなと市東1-1予定番号を指定すれば住所も分かりますが、それは指定された出荷先IDを経由するためです。 同じ出荷先なら、予定番号が違っても名称と住所は共通です。 出荷先の名称と住所は、出荷予定ではなく出荷先についての事実です。
このように、キーから別の列を経由して値が決まる関係を推移的関数従属と呼びます。 本例の出荷予定テーブルでは、出荷先IDは主キーではありません。
第3正規形は、第2正規形を満たした上で、このような主キー以外を経由する 非キー列の依存関係を解消した形です。本例では、出荷先IDで決まる名前と住所を、 出荷先IDを主キーとする別テーブルへ移すことで整理しました。
商品名も同様に、明細番号ではなく商品IDで決まる情報です。
第3章で作ったproductsを参照し、明細には商品IDを保存します。
商品分類名、容器種別名、取引先名にも、それぞれ専用のマスターテーブルがあります。
表記ゆれは、テーブルを分けるだけでは直らない
Section titled “表記ゆれは、テーブルを分けるだけでは直らない”配布CSVでは、商品の説明に缶、カン、PETという表記が混在しています。
正規化後は、明細へこの説明文をそのまま保存しません。
商品コードからproductsを参照し、さらにpackage_typesにある
容器種別コードと名称を使用します。これにより、名称を訂正する場所を一か所にできます。
ただし、正規化は、既に入力された誤字や表記ゆれを自動修正する機能ではありません。 マスターへ登録する値の基準を決め、移行時に元データを確認して、 正しいコードへ対応付ける作業が別に必要です。
この違いを整理すると、次のようになります。
| 観点 | この章での対応 |
|---|---|
| 保存場所の重複 | 正規化して専用テーブルへ分ける |
缶、カン、PETなどの表記ゆれ | 基準となるマスター値へ対応付ける |
| 今後の入力ミス | 選択肢、制約、入力チェックで防ぐ |
第2正規形と第3正規形の違いを、この例で確かめる
Section titled “第2正規形と第3正規形の違いを、この例で確かめる”| 段階 | 分割前に困ること | 作る別テーブル | 分割元に残すつなぎの列 |
|---|---|---|---|
| 第2正規形 | 同じ予定の日付を、明細ごとに直す必要がある | 出荷予定:一予定一行 | 明細に予定番号を残す |
| 第3正規形 | 同じ出荷先の住所を、予定ごとに直す必要がある | 出荷先:一出荷先一行 | 出荷予定に出荷先IDを残す |
第2正規形では、複合主キーの「予定番号だけ」で決まる情報に注目しました。 第3正規形では、主キーではない「出荷先ID」で決まる情報に注目しました。 どちらも、専用の別テーブルへ値を移し、参照するためのキーを残す点は共通です。
分けたテーブルは、番号をたどって一緒に使う
Section titled “分けたテーブルは、番号をたどって一緒に使う”図5-1(c)の明細から、20-001・明細2を見つけてください。
その行だけでは、出荷予定日も届け先の住所も表示されていません。
- 明細の予定番号
20-001から、出荷予定テーブルの同じ番号を探す - 出荷予定日
2026-11-20と出荷先ID1が分かる - 出荷先テーブルのID
1から、東配送センターの名前と住所が分かる
第4章で使ったJOINは、この「同じキーをたどる」処理をSQLで行う方法です。
| 予定番号 | 明細番号 | 出荷予定日 | 出荷先名 | 商品・数量 |
|---|---|---|---|---|
| 20-001 | 2 | 2026-11-20 | 東配送センター | PRD003・20ケース |
これは、三つのテーブルから必要な値を集めた表示用の結果です。 別々に保存していても、利用するときには一つの一覧として表示できます。
同じ番号が繰り返されるのは、悪い重複ではありませんか?
明細の予定番号や、出荷予定の出荷先IDは、別テーブルを参照するために必要です。 問題にしているのは「同じ予定の日付」「同じ出荷先の住所」などを別々に保存し、 修正漏れで食い違わせてしまうことです。正規化は、同じ値をすべて消す作業ではありません。
図の予定番号を、実装で使うIDへ対応させる
Section titled “図の予定番号を、実装で使うIDへ対応させる”ここまではデータの移動を追いやすくするため、予定番号でテーブルを結びました。
実際の完成版では、内部用のshipment_plan_id(出荷予定ID)を主キーにします。
業務上の番号shipment_plan_noも出荷予定テーブルに残し、UNIQUEで重複を防ぎます。
| 図中の予定番号 | 実際の出荷予定番号 | 完成版サンプルの出荷予定ID |
|---|---|---|
| 19-001 | SP20261119-001 | 1 |
| 20-001 | SP20261120-001 | 2 |
明細では予定番号の代わりに、この出荷予定IDを使って出荷予定を参照します。
商品も同様に、PRD001は商品ID 1、PRD003は商品ID 3として参照します。
| shipment_plan_id | line_no | product_id | case_quantity |
|---|---|---|---|
| 1 | 1 | 1 | 18 |
| 2 | 1 | 1 | 12 |
| 2 | 2 | 3 | 20 |
明細の主キーは、shipment_plan_idとline_noの複合主キーです。
同じ出荷予定の中で明細番号が重複しないようにします。
別の出荷予定では、明細番号を再び1から使えます。
IDへの置き換えは実装上の選択であり、正規化の新しい段階ではありません。 IDを追加するだけで、日付や住所の重複が自動的になくなるわけでもありません。
取引先と出荷先も、別の管理対象として保存する
Section titled “取引先と出荷先も、別の管理対象として保存する”注文元の会社と、実際に商品を届ける場所は同じとは限りません。 完成版では、一つの取引先が複数の出荷先を持てるようにします。
| 出荷先ID | 出荷先名 | 取引先ID |
|---|---|---|
| 1 | 東配送センター | 1 |
| 2 | 西店舗 | 1 |
| 3 | 中央倉庫 | 2 |
取引先名はcustomersにあり、取引先ID 1は取引先A、2は取引先Bです。
出荷先テーブルには取引先IDを保存し、会社の情報と届け先の情報を別々に管理します。
これにより、取引先単位の集計と、出荷先別の帳票作成を区別できます。
完成する七つのテーブル
Section titled “完成する七つのテーブル”この章では、第3章で作成した4テーブルへ3テーブルを追加します。
| 種類 | テーブル | 管理するもの |
|---|---|---|
| マスター | product_categories | 商品分類 |
| マスター | package_types | 容器種別 |
| マスター | products | 商品 |
| マスター | customers | 取引先 |
| マスター | delivery_destinations | 取引先ごとの出荷先 |
| 業務データ | shipment_plans | 出荷日、出荷先、状態など |
| 業務データ | shipment_plan_details | 商品とケース数 |
最初から七つを一度に暗記する必要はありません。 「第3章で作成済みの4テーブル」と「本章で追加する3テーブル」に分けて確認します。
図5-2の関係を、文章でも確認します。
- 一つの商品分類には、複数の商品が属する
- 一つの容器種別には、複数の商品が属する
- 一つの取引先は、複数の出荷先を持てる
- 一つの出荷先には、複数の出荷予定を作れる
- 一つの出荷予定は、複数の出荷予定明細を持てる
- 一つの商品は、複数の出荷予定明細に指定できる
完成版テーブルをMySQLへ追加する
Section titled “完成版テーブルをMySQLへ追加する”05-create-normalized-tables.sqlを開き、 ファイル全体を1回だけ実行します。
作成順は、外部キーで参照される側が先です。
delivery_destinationsshipment_plansshipment_plan_details
出荷先を作る
Section titled “出荷先を作る”CREATE TABLE delivery_destinations ( delivery_destination_id INT UNSIGNED NOT NULL AUTO_INCREMENT, customer_id INT UNSIGNED NOT NULL, destination_code VARCHAR(20) NOT NULL, destination_name VARCHAR(100) NOT NULL, postal_code VARCHAR(8) NOT NULL, address VARCHAR(200) NOT NULL, PRIMARY KEY (delivery_destination_id), CONSTRAINT uq_destinations_customer_code UNIQUE (customer_id, destination_code), CONSTRAINT fk_destinations_customer FOREIGN KEY (customer_id) REFERENCES customers (customer_id)) ENGINE = InnoDB;destination_codeは、取引先の中で出荷先を識別するコードです。
この教材ではcustomer_idとdestination_codeの組み合わせを
UNIQUEにし、同じ取引先内での重複を防ぎます。
出荷予定を作る
Section titled “出荷予定を作る”出荷予定には商品を直接保存しません。 出荷予定全体で一つに決まる値だけを保存します。
CREATE TABLE shipment_plans ( shipment_plan_id INT UNSIGNED NOT NULL AUTO_INCREMENT, shipment_plan_no VARCHAR(20) NOT NULL, shipment_date DATE NOT NULL, delivery_destination_id INT UNSIGNED NOT NULL, shipment_status VARCHAR(20) NOT NULL, scheduled_time_slot VARCHAR(20) NOT NULL, updated_at DATETIME NOT NULL, PRIMARY KEY (shipment_plan_id), CONSTRAINT uq_shipment_plans_no UNIQUE (shipment_plan_no), CONSTRAINT fk_shipment_plans_destination FOREIGN KEY (delivery_destination_id) REFERENCES delivery_destinations (delivery_destination_id)) ENGINE = InnoDB;予定状態にはDRAFT、CONFIRMED、CANCELLED、SHIPPEDを使用します。
便にはAMとPMを使用します。
MySQLの正確なバージョンが確定していないため、初版では
CHECK制約を使わず、許可値をデータ辞書と登録SQLで管理します。
出荷予定明細を作る
Section titled “出荷予定明細を作る”CREATE TABLE shipment_plan_details ( shipment_plan_id INT UNSIGNED NOT NULL, line_no SMALLINT UNSIGNED NOT NULL, product_id INT UNSIGNED NOT NULL, case_quantity SMALLINT UNSIGNED NULL, PRIMARY KEY (shipment_plan_id, line_no), CONSTRAINT fk_shipment_details_plan FOREIGN KEY (shipment_plan_id) REFERENCES shipment_plans (shipment_plan_id), CONSTRAINT fk_shipment_details_product FOREIGN KEY (product_id) REFERENCES products (product_id)) ENGINE = InnoDB;case_quantityは、仮登録の段階では数量未定を表せるようNULLを許可します。
確定済みの出荷予定では1以上を必須にする、という業務ルールは、
登録を行うアプリケーションと運用手順でも確認します。
SQLファイルでは、外部キーの更新と削除にRESTRICTを明示しています。
参照されている取引先、商品、出荷予定を誤って変更・削除することを防ぐためです。
正規化後のデータを登録する
Section titled “正規化後のデータを登録する”05-insert-normalized-data.sqlを開き、 ファイル全体を1回だけ実行します。
登録される件数は次のとおりです。
| テーブル | 件数 |
|---|---|
delivery_destinations | 3 |
shipment_plans | 5 |
shipment_plan_details | 7 |
出荷予定SP20261120-001は、出荷予定テーブルでは1行、
明細テーブルでは2行になります。
出荷予定テーブル(関係する列を抜粋)
| 出荷予定ID | 出荷予定番号 | 出荷予定日 | 出荷先ID | 状態 |
|---|---|---|---|---|
| 2 | SP20261120-001 | 2026-11-20 | 1 | CONFIRMED |
出荷予定明細テーブル
| 出荷予定ID | 明細番号 | 商品ID | ケース数 |
|---|---|---|---|
| 2 | 1 | 1 | 12 |
| 2 | 2 | 3 | 20 |
出荷予定日や状態を一度だけ保存しながら、商品は必要な数だけ追加できます。
両方の出荷予定ID 2が対応します。明細に出荷予定番号や商品名を直接保存せず、
出荷予定や商品のテーブルから参照する点も確認してください。
正規化の前後をSQLで照合する
Section titled “正規化の前後をSQLで照合する”正規化後のテーブルを作成できても、途中でデータを欠落させてはいけません。 05-verify-normalization.sqlを使って確認します。
件数を確認する
Section titled “件数を確認する”SELECT COUNT(*) AS plan_countFROM shipment_plans;
SELECT COUNT(*) AS detail_countFROM shipment_plan_details;出荷予定は5件、明細は7件です。
正規化前のshipment_schedule_rowsの7明細と、件数が一致することを確認します。
件数だけでは商品の取り違えなどを検出できないため、続けて結合した値も照合します。
結合して元の形へ戻せることを確認する
Section titled “結合して元の形へ戻せることを確認する”正規化は、情報を使えなくする分割ではありません。 外部キーをたどって結合すれば、業務に必要な形へ戻せます。
SELECT sp.shipment_plan_no, sp.shipment_date, c.customer_name, dd.destination_name, p.product_name, spd.case_quantity, sp.shipment_statusFROM shipment_plans AS spINNER JOIN delivery_destinations AS dd ON sp.delivery_destination_id = dd.delivery_destination_idINNER JOIN customers AS c ON dd.customer_id = c.customer_idINNER JOIN shipment_plan_details AS spd ON sp.shipment_plan_id = spd.shipment_plan_idINNER JOIN products AS p ON spd.product_id = p.product_idORDER BY sp.shipment_plan_no, spd.line_no;結果は7行です。 出荷予定の情報は一度だけ保存されていますが、検索結果では明細ごとに表示できます。
第4章と同じ集計結果になることを確認する
Section titled “第4章と同じ集計結果になることを確認する”2026年11月20日の確定済み缶飲料を合計します。
SELECT SUM(spd.case_quantity) AS total_casesFROM shipment_plans AS spINNER JOIN shipment_plan_details AS spd ON sp.shipment_plan_id = spd.shipment_plan_idINNER JOIN products AS p ON spd.product_id = p.product_idINNER JOIN package_types AS pt ON p.package_type_id = pt.package_type_idWHERE sp.shipment_date = '2026-11-20' AND sp.shipment_status = 'CONFIRMED' AND pt.package_type_code = 'CAN';結果は、第4章と同じ29ケースです。
テーブルの保存構造を変えても、業務上の答えが変わっていないことを確認できます。
正規化後も業務ルールは別に必要になる
Section titled “正規化後も業務ルールは別に必要になる”第3正規形まで整理すれば、すべての問題が自動的に解決するわけではありません。 次のようなルールは、データ辞書、制約、アプリケーション、運用手順で補います。
| 業務ルール | 初版での扱い |
|---|---|
| 確定済みのケース数は1以上 | アプリと確認手順で検査する |
| 状態は4種類だけ | データ辞書と登録処理で制限する |
| 出荷予定番号は重複させない | UNIQUE制約で防ぐ |
| 存在しない商品を明細へ入れない | 外部キーで防ぐ |
| 取引先内で出荷先コードを重複させない | 複合UNIQUE制約で防ぐ |
| AIは確定済みを既定の対象にする | 第6章の検索機能で条件を固定する |
正規化はデータ構造の基礎です。 その上に、業務に合った制約と安全な利用方法を組み合わせます。
AIが利用するときに正規化が役立つ理由
Section titled “AIが利用するときに正規化が役立つ理由”AIは、曖昧な業務ルールを自動的に正しく推測できるとは限りません。 正規化された構造では、次の事実を検索条件として明示しやすくなります。
- 出荷予定日と予定状態は
shipment_plansにある - ケース数と商品は
shipment_plan_detailsにある - 容器種別は
productsからpackage_typesを参照する - 取引先と出荷先は別の管理対象である
- 単位換算に使う入数は
products.units_per_caseにある
一方、AIへ七つの物理テーブルを自由に解釈させる構成にはしません。 第6章では、人が確認した結合条件と検索項目を参照用ビューにまとめ、 読み取り専用の機能から利用します。
演習4:第1~第3正規形へ整理する
Section titled “演習4:第1~第3正規形へ整理する”課題1:三つの問題を分類する
Section titled “課題1:三つの問題を分類する”次の状況が、更新、追加、削除のどの操作で起きるかを書いてください。
| 状況 | 操作の種類 |
|---|---|
| 商品名を一行だけ直し、新旧の名称が混在した | |
| 出荷予定のない新商品を一覧へ記録できない | |
| 最後の出荷予定を消すと、出荷先の住所も消えた |
課題2:第1正規形へ直す
Section titled “課題2:第1正規形へ直す”05-normalization-source.csvのうち、出荷予定SP20261120-001を確認します。
商品1と商品2を、一商品一明細行へ書き換えてください。
| 出荷予定番号 | 明細番号 | 商品コード | ケース数 |
|---|---|---|---|
| SP20261120-001 | |||
| SP20261120-001 |
課題3:出荷予定と明細の別テーブルへ、値を写し分ける
Section titled “課題3:出荷予定と明細の別テーブルへ、値を写し分ける”図5-1(b)の分割前の3行を、下の二つのテーブルへ写し分けてください。 ここでは図と同じ短縮した予定番号を使います。
出荷予定テーブル
| 予定番号 | 出荷予定日 |
|---|---|
| 19-001 | |
| 20-001 |
出荷予定明細テーブル
| 予定番号 | 明細番号 | 商品コード | ケース数 |
|---|---|---|---|
| 19-001 | |||
| 20-001 | |||
| 20-001 |
書き終わったら、次の二点を確認します。
- 20-001の日付を変えるとき、どのテーブルの何行を修正するか
- 予定番号を明細テーブルから消すと、どの対応が分からなくなるか
課題4:出荷先の名前と住所を、もう一つの別テーブルへ移す
Section titled “課題4:出荷先の名前と住所を、もう一つの別テーブルへ移す”図5-1(c)では、19-001と20-001が同じ東配送センターを指定しています。 重複している名前と住所を、出荷先専用のテーブルへ一度だけ書いてください。 住所は図と同じ短縮表示で構いません。
出荷先テーブル
| 出荷先ID | 出荷先名 | 住所 |
|---|---|---|
| 1 |
出荷予定テーブルには、名前と住所の代わりに何を残せばよいでしょうか。 その値を書いてください。
| 予定番号 | 出荷先ID |
|---|---|
| 19-001 | |
| 20-001 |
さらに、20-001の明細2から出荷先名まで、どの番号をたどるか説明してください。
課題5:完成版を構築する
Section titled “課題5:完成版を構築する”05-create-normalized-tables.sqlを1回実行する- SCHEMASを更新し、追加された3テーブルを確認する
05-insert-normalized-data.sqlを1回実行する- 3件、5件、7件の順に登録されたことを確認する
05-verify-normalization.sqlを実行する- 明細7行と、確定済み缶飲料29ケースを確認する
確認欄:
- 七つの完成版テーブルが表示された
- 出荷予定は5件、出荷予定明細は7件だった
-
SP20261120-001に明細が2行あった - 11月20日の確定済み缶飲料は29ケースだった
- 取引先名と出荷先名を別々に表示できた
課題6:自社の業務へ置き換える
Section titled “課題6:自社の業務へ置き換える”自社で一つの一覧に混在している可能性がある情報を一つ選びます。 実データや個人情報は書かず、項目名だけで考えてください。
- その一覧の一行は、何の一件か
- 同じ値が複数行へ繰り返されている列は何か
- 一部だけ更新すると矛盾する列は何か
- マスターデータとして分けられそうな管理対象は何か
- 分ける場合、両方のテーブルにどの番号やIDを残すか
演習結果を確認する
課題1
- 商品名の新旧が混在:更新時の問題
- 新商品を記録できない:追加時の問題
- 住所まで失う:削除時の問題
課題2
| 出荷予定番号 | 明細番号 | 商品コード | ケース数 |
|---|---|---|---|
| SP20261120-001 | 1 | PRD001 | 12 |
| SP20261120-001 | 2 | PRD003 | 20 |
課題3
出荷予定テーブルへ、日付を予定ごとに一度だけ保存します。
| 予定番号 | 出荷予定日 |
|---|---|
| 19-001 | 2026-11-19 |
| 20-001 | 2026-11-20 |
明細は3行のまま残します。
| 予定番号 | 明細番号 | 商品コード | ケース数 |
|---|---|---|---|
| 19-001 | 1 | PRD001 | 18 |
| 20-001 | 1 | PRD001 | 12 |
| 20-001 | 2 | PRD003 | 20 |
日付変更では、出荷予定テーブルの20-001の一行だけを修正します。 明細から予定番号を消すと、どの出荷予定に属する商品と数量なのか分からなくなります。 明細番号1は別の出荷予定でも使われるため、明細番号だけでは対応付けられません。
課題4
| 出荷先ID | 出荷先名 | 住所 |
|---|---|---|
| 1 | 東配送センター | 東1-1 |
二つの出荷予定には、どちらも出荷先ID 1を残します。
明細の予定番号20-001 → 出荷予定の20-001 → 出荷先ID 1 →
出荷先の東配送センター、という順にたどれます。
出荷先名と住所の訂正箇所は出荷先テーブルの一行だけです。
よくあるエラーと確認場所
Section titled “よくあるエラーと確認場所”| 状況 | 主な原因 | 最初に確認すること |
|---|---|---|
| テーブルが既に存在する | 作成用SQLを再実行した | SCHEMASに3テーブルがあるか |
| 外部キーエラーになる | 親データがない、実行順が違う | 取引先、商品、出荷先、出荷予定の順 |
| 重複エラーになる | 同じデータを2回登録した | 業務コードと主キーの既存行 |
| 明細が登録できない | 出荷予定IDか商品IDが存在しない | 親テーブルをSELECTする |
| 結合結果が7行にならない | ONの条件不足、データ不足 | 主キーと外部キーの対応 |
| 合計が29にならない | 日付、状態、容器の条件不足 | 集計前の4明細を表示する |
エラーが出た場合は、同じファイルを何度も実行せず、 どのSQL文まで成功したかをAction Outputで確認してください。
この章で作成・確認したもの
Section titled “この章で作成・確認したもの”- 正規化前の一覧で起きる更新、追加、削除時の問題
- 一商品一明細行にした第1正規形
- 出荷予定と明細へ分けた第2正規形
- 取引先、出荷先、商品分類、容器種別を分けた第3正規形
- 出荷先、出荷予定、出荷予定明細の3テーブル
- 全7テーブルの関係図
- 出荷予定5件と出荷予定明細7件
- 正規化前後で明細数と29ケースの集計結果が一致すること
- 第6章でAI検索に使用する完成版データベース
この章のまとめ
Section titled “この章のまとめ”- 正規化は、データの追加、更新、削除で矛盾が起きにくい構造へ整理することです。
- 第1正規形への整理では、商品1・商品2の欄を、商品ごとの明細行へ直しました。
- 第2正規形への整理では、予定番号だけで決まる日付や状態を、別の出荷予定テーブルへ移しました。
- 第3正規形への整理では、出荷先IDで決まる名前と住所を、別の出荷先テーブルへ移しました。
- 移した情報は専用テーブルに一度だけ保存し、元のテーブルには参照するための番号やIDを残します。
- 予定番号や出荷先IDの繰り返しは、テーブル同士を対応付けるために必要です。
- 分けたテーブルは、主キーと外部キーをたどる
JOINで、一つの一覧として表示できます。 - 正規化後は、件数と集計結果を照合し、情報が失われていないことを確認します。
- AIが使う場合も、日付、状態、商品、数量、単位の意味を明確にする必要があります。
次章では、完成したデータベースを読み取り専用の参照用ビューにまとめ、 ローカルAIから検索して出荷指示書の下書きへ利用します。