開発支援・コーディング

DB設計と正規化入門|同じデータを2箇所に書いた瞬間、いつか矛盾する

正規化とは結局何をすることなのか1NF・2NF・3NFを暗記する必要はあるのか個人開発ではどこまで正規化すべきか

顧客が引っ越して住所を更新した。ところが古い注文の画面では、まだ前の住所が表示されている——同じ「顧客の住所」というデータを、複数の注文レコードにコピーして持っていたのが原因です。1箇所を直しても、コピーされた他の場所は直らずに残ります。

これが更新異常で、正規化はこの種の矛盾を構造的に防ぐ技術です。この記事は理論の暗記ではなく、実際にコードを実行して矛盾が起きる様子とその解決を見せます。1NF・2NF・3NFという用語は最後に、必要な分だけ触れます。

この記事の要点

  • 正規化の目的は矛盾を構造的に起こさなくすること。理論より先に「同じデータを2箇所に置かない」が本質
  • 更新異常は1箇所だけ直して他が古いまま残る現象。テーブル分割で構造的に防げる
  • 1NF・2NF・3NFは依存関係の種類ごとの点検項目。実務では3NFまで意識すれば大半をカバーできる
  • 正規化しすぎるとJOINが増えて読みにくくなる。個人開発では検索頻度の高い箇所は意図的に崩してよい

更新異常を実際に起こしてみる——理論より先に現象

正規化は用語の暗記ではなく「同じ事実を1箇所にだけ持つ」設計。この記事のコードは実際に矛盾を再現し、テーブル分割だけで解消している。

DB設計と正規化の理論は、E.F. Coddが1970年代に定式化した第三正規形(3NF)の定義に集約されます。“every non-prime attribute of R is non-transitively dependent on each candidate key”。各非キー属性が、候補キーに間接的にではなく直接依存していること——これが定義です。難解な表現ですが、実際に起きる問題を見てから読むと理解が変わります。

実際に動かして確かめる

以下は編集部が実際に実行したコードと、その出力です。顧客の住所を複数の注文行にコピーして持つ「未正規化」のテーブルで、住所を更新してみます。

起きたこと

db-anomaly-demo.mjs(実行済み。編集部環境で検証)javascript
// ❌ 未正規化: 顧客の住所を複数の注文行に重複して持つ
let ordersFlat = [
  { orderId: 1, customerId: "C1", customerAddress: "東京都渋谷区1-1", item: "ノートPC" },
  { orderId: 2, customerId: "C1", customerAddress: "東京都渋谷区1-1", item: "モニター" },
  { orderId: 3, customerId: "C2", customerAddress: "大阪府大阪市2-2", item: "キーボード" },
];

function updateAddressFlat(customerId, newAddress) {
  const target = ordersFlat.find((o) => o.customerId === customerId);
  target.customerAddress = newAddress; // うっかり1行だけ更新
}
実行結果(そのまま。加工なし)text
=== 未正規化テーブルで C1 の住所を更新 ===
[
  { "orderId": 1, "customerId": "C1", "customerAddress": "東京都新宿区9-9", "item": "ノートPC" },
  { "orderId": 2, "customerId": "C1", "customerAddress": "東京都渋谷区1-1", "item": "モニター" },
  { "orderId": 3, "customerId": "C2", "customerAddress": "大阪府大阪市2-2", "item": "キーボード" }
]

→ orderId:1 は新住所、orderId:2 は旧住所のまま。同じ顧客なのにデータが矛盾(更新異常)

orderId 1と2は同じ顧客C1のはずですが、更新後は住所が食い違っています。これが更新異常で、原因はコードのバグではなくデータ構造そのもの——同じ事実(C1の住所)を2行に重複して持ったことです。

A relation R is in 3NF if and only if it is in second normal form (2NF) and every non-prime attribute of R is non-transitively dependent on each candidate key.
出典Wikipedia「Third normal form」(E.F. Coddの定義の引用元) 一次情報を確認2026-08-14 この内容の有効期限2027-02-14

テーブルを分けるだけで、矛盾は構造的に消える

解決策は複雑な理論ではなくテーブル分割。顧客テーブルを切り出し外部キーで参照すれば、更新は1箇所で済み矛盾が起きようがなくなる。

DB設計と正規化の実務的な解決は、重複しているデータを別テーブルに切り出すことです。同じ実行環境で、正規化した後のテーブルに同じ更新をしてみます。

テーブルを分割した結果

実行結果(正規化後。同じ操作の続き)text
=== 正規化後のテーブルで C1 の住所を更新 ===
[
  { "orderId": 1, "customerId": "C1", "item": "ノートPC", "address": "東京都新宿区9-9" },
  { "orderId": 2, "customerId": "C1", "item": "モニター", "address": "東京都新宿区9-9" },
  { "orderId": 3, "customerId": "C2", "item": "キーボード", "address": "大阪府大阪市2-2" }
]

→ 1箇所の更新で全注文の住所が一致。矛盾が構造的に起きない

顧客テーブルを分離し、注文テーブルはcustomerIdで参照するだけにしました。住所を更新する処理は顧客テーブルの1行を触るだけで済み、矛盾を起こす経路そのものが構造からなくなります。このテーブル分割の発想はクリーンアーキテクチャで扱う「詳細を後から差し替え可能にする」考え方とも通じます。

1NF・2NF・3NFは「点検の段階」

正規形点検すること違反の例
1NF1つのセルに複数の値を詰めていないかtagsカラムに"AI,自動化,SaaS"とカンマ区切りで格納
2NF複合キーの一部にしか依存しない列がないか受注明細テーブルに商品名(商品IDだけに依存)を直接持つ
3NF非キー列が他の非キー列に依存していないか(間接依存)注文テーブルに顧客住所(顧客IDに依存、注文IDには非依存)を持つ
表1 ── 3つの正規形を実務の点検項目として読む
この節は一次情報での裏取りが未了です。 編集部の整理(正規化による解決)(2026-08-14 記載)

正規化は絶対善ではない——崩す判断も設計のうち

まず正規化して始め、実測してから崩す。最初から非正規化するのは、問題が起きる前に複雑さを買うことになる。

DB設計と正規化を学ぶと、全てを正規化したくなりますが、行き過ぎには代償があります。正規化するほどテーブルが増え、1件の表示に必要なJOINも増えます。アクセス頻度が高い画面では、この分割自体が性能のボトルネックになることがあります。

非正規化という選択肢

在庫数のように頻繁に参照される集計値を、都度JOINして計算する代わりに非正規化した列として持ち、更新のたびに計算し直す設計もあります。これは正規化を知らないミスではなく、意図的なトレードオフです。ただし順番が重要で、最初は正規化して設計し、実際のアクセスパターンを計測してから、必要な箇所だけ崩すのが安全です。

崩す順番を間違えない

余談 個人開発での現実的な落とし所

個人開発の初期段階では、パフォーマンス問題が顕在化する前に非正規化を持ち込むと、更新異常のリスクだけを先取りして背負うことになります。編集部の見立てでは、最初は素直に正規化し、ボトルネックが実測されてから崩すのが、個人開発として現実的な優先順位です。

この節は一次情報での裏取りが未了です。 編集部の整理(正規化とパフォーマンスのトレードオフ)(2026-08-14 記載)

よくある質問

1NF・2NF・3NFを全部覚える必要がありますか?
用語より先に「同じ事実を複数箇所に書いていないか」を確認する習慣の方が実務的です。3つの正規形はその点検を段階的に厳密化したものと理解すれば、暗記の負担は減ります。
個人開発では正規化しすぎない方がいいですか?
正規化とパフォーマンスはトレードオフです。読み取り頻度が高い箇所では、意図的に非正規化(集計値の複製など)することもあります。ただし最初は正規化した設計で始め、実測してから崩す順番が安全です。
NoSQLでも正規化は関係ありますか?
考え方は関係します。ドキュメントDBでも同じ情報を複数のドキュメントに埋め込めば、更新時に同じ矛盾のリスクを抱えます。埋め込むか参照するかの判断に、正規化の発想がそのまま使えます。
既存の正規化されていないテーブルはどう直せばいいですか?
重複しているデータの単位でテーブルを切り出し、外部キーで参照する形に変更します。この記事のコード例が、その最小の実演になっています。

まとめ

  • 正規化の本質は「同じ事実を1箇所にだけ書く」。用語より先にこの発想を持つ
  • 更新異常は実際に矛盾したデータを生む。テーブル分割で構造的に防げる
  • 3NFまでの点検で個人開発の実務は大半カバーできる
  • 正規化は絶対善ではない。パフォーマンスとのバランスで意図的に崩す場面もある

今日から始められること

  1. 自分のテーブルで、同じ値が複数行に重複しているカラムを探す
  2. 重複しているデータを別テーブルに切り出し、外部キーで参照する形に変える
  3. 更新処理が「1箇所を直せば全体に反映される」構造になっているか確認する
  4. 検索頻度の高いクエリでJOINが重い場合のみ、非正規化を検討する

実務で組んだDB設計と正規化のワークフローには、値段が付きます

同じ課題を持つ会社にとって、動いている設定は「作る時間」を買えるということです。ServiceDockは自作のワークフローやテンプレートを出品できるマーケットプレイスです。手数料や出品の流れは出品者向けページにまとまっています。

出品の仕組みを見る