データベースの正規化とは、同じ値を 2 か所に置かない設計

回 5-1 道 5 データベースと分散

データベースの正規化は、同じ事実を 1 か所にだけ書くように表を分ける設計です。同じ値が 2 か所にあると、1 か所だけ直して食い違う更新の異常、注文が無いと商品を登録できない挿入の異常、注文を消すと商品まで消える削除の異常が起きます。第 1〜第 3 正規形の順に表を分けるとこの 3 つは起きなくなりますが、読むたびに表を結合する手間が増えます。

同じ値が 2 か所にあると何が壊れるかを、更新・挿入・削除の 3 つの異常に切り分けられるようになります。

同じ住所を 3 枚の注文票に書くと、何が困るのか

引っ越しのときに 1 枚だけ直すと、同じ人の住所が 2 つになって、どちらが正しいか分からなくなります。同じ値を 2 か所以上に書くと、直し漏れが食い違いになって残ります。

データベースの正規化 (せいきか) は、この食い違いを起こさないために、同じ事実を 1 か所にだけ書くように表を分ける設計です。データを入れ始める前に、表の分け方として決めておきます。

ふつうの Web のサービスでは、商品、注文、会員といった値を、行と列の表 (テーブル) に入れておきます。表を 1 枚にまとめると作るのは楽ですが、同じ商品の名前が注文の数だけ並びます。

この回では、注文の表を例に、同じ値が 2 か所にあると何が壊れるかを Python の表で実際に起こします。そのあと第 1、第 2、第 3 正規形の順に表を分け、同じ操作をしても壊れなくなることを確かめます。

同じ住所がどの注文票にも書いてあり、1 枚だけ直すと食い違うので、住所録に 1 回だけ書いて注文は番号で指す様子の図

上の図は住所録の例です。住所を住所録に 1 回だけ書き、注文票は顧客の番号で指せば、直す場所は 1 つで済みます。

正規化はどう表を分けるのか

正規化は、「この列の値は、どの列が決まれば決まるのか」を見て、決まり方が違う列を別の表へ移す作業です。移したあとは、番号 (キー) で表どうしを指し合います。

同じ値が 2 か所にあると起きる 3 つの異常

1 枚の表に、注文と商品と分類をまとめて書いたとします。

注文商品名前分類分類名個数
1P1ノートPCC1パソコン1
1P2マウスC2周辺機器2
2P1ノートPCC1パソコン1
3P3キーボードC2周辺機器1

ノートPC という名前と、パソコン という分類名が 2 行ずつ書かれています。この表では次の 3 つの異常 (いじょう) が起きます。

異常何をすると何が壊れるか
更新の異常分類名を 1 行だけ直す同じ分類に名前が 2 つできる
挿入の異常まだ売れていない商品を足す注文の番号が無いので行を作れない
削除の異常注文 3 を消すキーボード という商品の情報まで消える

3 つとも原因は同じで、寿命が違う事実を 1 つの行に混ぜたことです。注文は毎日増えて消えますが、商品や分類は注文と関係なく長く残ります。

主キーと関数従属

主キー (しゅキー) は、表の 1 行を 1 つに決める列、または列の組です。上の表では、注文 1 に商品が 2 つあるので注文だけでは行が決まらず、(注文, 商品) の組が主キーになります。

関数従属 (かんすうじゅうぞく) は、「列 X の値が決まれば、列 Y の値が 1 つに決まる」という関係です。商品が P1 なら名前は必ずノートPC なので、名前は商品に従属します。正規化は、この従属の向きを見て表を分けます。

第 1 正規形 1 マスに 1 つの値

第 1 正規形は、表のどのマスにも値が 1 つだけ入っている形です。注文 1 の商品の欄に「ノートPC,マウス」と 2 つを書いた表は、まだ第 1 正規形ではありません (非正規形と呼びます)。

1 マスに 2 つあると、「マウスを買った注文」を探すときに文字を切り分けて調べる手間が要ります。1 行 1 商品に分ければ、等しいかを比べるだけで探せます。

第 2 正規形 主キーの一部だけで決まる列を出す

第 2 正規形は、第 1 正規形で、しかも主キーの一部だけで決まる列が無い形です。上の表の主キーは (注文, 商品) ですが、名前と分類は商品だけで決まります。この「主キーの一部への従属」を部分関数従属と呼びます。

名前と分類を商品の表へ移すと、注文の明細の表に残るのは、(注文, 商品) の組で初めて決まる個数だけになります。

第 3 正規形 キーでない列を経由する従属を出す

第 3 正規形は、第 2 正規形で、しかもキーでない列が、別のキーでない列を経由して主キーから決まる関係が無い形です。商品の表では、商品が分類を決め、分類が分類名を決めます。商品から分類名が、分類を経由して決まる関係を推移的関数従属と呼びます。

分類名を分類の表へ移すと、分類名を書く場所は 1 か所になります。ここまで分けた表は次の 3 枚です。

表主キー残る列
明細(注文, 商品)個数
商品商品名前、分類
分類分類分類名

下の図で、乱れた表に手順 1 から 3 を順に当てると、表が何枚に割れるかを確かめられます。

手順を当てると表が割れていく

動かして確かめる

1 本目は、第 1 正規形にする前の表です。1 マスに 2 つ入った商品の欄を切り分け、1 行 1 商品にします。

動かす 1 マスに 1 つの値にする

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

(1, 'ノートPC')
(1, 'マウス')
(2, 'ノートPC')
マウス [1]

2 行だった表が 3 行になり、どのマスも値が 1 つになりました。マウスを買った注文は、等しいかを比べるだけで 1 だと分かります。

2 本目は、上の 4 行の表 (第 1 正規形だが第 2 正規形ではない表) で、3 つの異常を順に起こします。

動かす 1 枚の表で 3 つの異常を起こす

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

更新 ['PC', 'パソコン']
挿入 False
削除 False
行の数 3

分類 C1 の名前を 1 行目だけ PC に直すと、C1 には PC と パソコン の 2 つの名前ができました。どちらが正しいかは、表を見ても分かりません。

まだ注文の無いタブレットは、主キーの注文が欠けるので入りませんでした (False)。注文 3 を消すと行は 3 行になり、P3 のキーボードはどの行にも残りません (False)。消したかったのは注文だけでした。

3 本目は、第 3 正規形まで分けた 3 枚の表で、同じ 3 つの操作をします。

動かす 分けた表で同じ操作をする

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

更新 ['PC']
挿入 True
削除 キーボード
明細の数 3
(1, 'ノートPC', 'PC', 1)
(1, 'マウス', '周辺機器', 2)
(2, 'ノートPC', 'PC', 1)

分類名は分類の表に 1 か所しか無いので、直すのも 1 か所で、結合して戻した表でもノートPC の分類名は PC の 1 つだけでした。

タブレットは注文が無くても商品の表に入り (True)、注文 3 の明細を消してもキーボードは商品の表に残りました。最後の 3 行は、view() で 3 枚の表を結合して 1 枚に戻した姿です。

分けすぎると何が起きるのか

正規化した表は、読むたびに別の表を引き合わせる (結合する) 手間がかかります。分ける表が増えるほど、1 行を読むために引く表も増えます。

4 本目は、明細 100 行を分類名つきで読むときに、別の表を何回引くかを数えます。後半は、その手間を省くために注文の合計をわざと注文の側に持ち (非正規化)、1 か所だけ直し漏れを起こします。

動かす 結合の手間と、わざと崩した合計

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

明細 100
引いた回数 200
持った合計 98000
数え直し 102000

明細 100 行を読むのに、商品の表と分類の表を 1 回ずつ、合わせて 200 回引きました。1 枚の表なら、名前も分類名も同じ行にあるので別の表を引きません。本物のデータベースでは、結合は索引 (さくいん) などで速くできますが、表が増えるほど読む手間が増える向きは変わりません。

後半では、合計を注文の側に持ったので、明細を足すたびに合計も直す必要があります。2 つ目の明細で合計を直し忘れると、持った合計は 98000、明細から数え直すと 102000 になり、同じ注文に 2 つの合計ができました。わざと崩すと、読みは速くなる代わりに、この回の最初の更新の異常が戻ってきます。

読みを何行減らすために 書きを 1 行増やすか

上の図は、会員の表に注文の合計を持たせる例です。注文の件数を増やすと、毎回数える側は読む行が増え、合計を持つ側は書く先が 1 つ増えます。

非正規化 (ひせいきか) を選ぶのは、読みがとても多く、結合が遅いと測って分かったときです。選ぶなら、合計を直す処理を 1 か所にまとめ、ときどき明細から数え直して食い違いが無いかを確かめます。

同じ値に見えて、別の事実のもの

すべての重なりが悪いわけではありません。注文の明細に「買った時の値段」を書くのは、商品の今の値段とは別の事実なので、重複ではありません。

動かす 値段を商品の表だけに置くと

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

値上げ前 98000
値上げ後 108000
残した値段 98000

値段を商品の表だけに置くと、値上げした瞬間に先月の注文の合計まで 98000 から 108000 に変わりました。明細に買った時の値段を残せば、先月の注文は 98000 のままです。正規化で消すのは「同じ事実の写し」で、「時点の違う別の事実」は残します。

正規化できているかを仕事でどう確かめるのか

仕事では、表を作る前に「この列は何が決まれば決まるか」を 1 列ずつ問い、主キー以外で決まる列が無いかを確かめます。

  1. 表ごとに主キーを書き出す。複数の列の組なら、組のどの部分で決まる列があるかを見る
  2. キーでない列から決まる列 (分類から分類名) が無いかを見る
  3. 同じ名前や住所を書く列が、2 つ以上の表に無いかを見る
  4. わざと残した重なり (合計、買った時の値段) には、なぜ残したかを書き残す

すでにデータがあるなら、列 X の同じ値に、列 Y の違う値が混ざっていないかを数えると、従属を手元で確かめられます。

動かす 列が列を決めるかを数える

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

実行すると、次の通りに出ます

商品 -> 名前 True
商品 -> 分類 True
分類 -> 分類名 True
注文 -> 名前 False
注文 -> 個数 False

商品は名前と分類を決め、分類は分類名を決めました。注文だけでは名前も個数も決まりません。主キー (注文, 商品) の一部の商品が名前を決めるので第 2 正規形ではなく、キーでない分類が分類名を決めるので第 3 正規形でもない、と読めます。

ただし、今あるデータで True でも、将来の行で崩れることがあります。数えた結果は手がかりで、決まり方は業務の約束 (1 つの商品に名前は 1 つ) から決めます。

分けた表は、SQL では結合 (JOIN) で 1 枚に戻して読みます。上の 3 本目の view() と同じことを書くと、次の形になります。

分けた表を結合して読む

SELECT l.order_id, p.name,
       c.name, l.qty
FROM lines l
JOIN products p
  ON p.id = l.product_id
JOIN categories c
  ON c.id = p.category_id;

関わる実例

正規化の不備そのものを原因とした、会社の公表された事後報告は、この回では確かめられませんでした。代わりに、正規化という言葉と考え方がどこから来たかを、元の論文で読みます。

正規化はどこから来たか、E. F. Codd の 1970 年の論文

1970年6月、IBM の研究所 (カリフォルニア州サンノゼ) の E. F. Codd が、Communications of the ACM の第 13 巻 6 号に「A Relational Model of Data for Large Shared Data Banks」を出しました。データを表 (関係) として扱う、今の関係データベースの元になった論文です。ACM の文献の頁によると、この論文は 1969年8月19日の IBM の研究報告 RJ599 を直した版です。

論文の要旨は、大きなデータバンクを使う人を、データが機械の中でどう並んでいるかを知らなければならない状態から守るべきだ、と書き出しています。

1.4 節「NORMAL FORM」で、Codd は、値の中にさらに表を抱えた「単純でない定義域」を取り除く手順を normalization (正規化) と名付けました。例は社員の表で、職歴と子どもが社員の中に入れ子になり、給与の履歴が職歴の中に入っています。親の主キー (社員の番号) を子の表へ写して入れ子をほどき、社員、職歴、給与の履歴、子どもの 4 枚に分けます。1 マスに 1 つの値にする、この回の 1 本目と同じ向きの作業です。

同じ節は「正規化の種類の操作はほかにもあるが、この論文では扱わない」と書いています。ACM の文献の頁によると、関数従属と最初の 3 つの正規形を持ち込んだのは、1972 年の「Further Normalization of the Data Base Relational Model」です。

2.3 節「CONSISTENCY」は、重なり (redundancy) を持つ表の組では食い違い (inconsistency) が起き得るとし、挿入、削除、キーの更新のたびに食い違いを調べるやり方を挙げています。続けて、その調べはこれらの操作を遅くすると書いています。同じ値を 2 か所に置くと、食い違いを防ぐ手間がどこかに必要になる、というこの回の話は、論文の時点で書かれていました。

Codd は 1981 年に ACM のチューリング賞を受けました。授賞の理由は「データベース管理システムの理論と実践への、基本的で継続的な貢献」です。

崩れた前提は「使う人はデータの並べ方を知っている」です。並べ方に頼らず、同じ事実を 1 か所に置いて表を番号で指し合う考え方が、ここから始まりました。

出典は 論文の写し (ペンシルベニア大学の講義の頁に置かれた PDF)、Codd の紹介 (ACM A.M. Turing Award)、Codd の文献の頁 (ACM A.M. Turing Award) です。

よくある問い

どこまで正規化すればよいのですか

この回では、第 3 正規形までを 1 つの目安にしています。第 3 正規形まで分けると、キーでない列を経由して決まる列が無くなり、更新・挿入・削除の 3 つの異常は、この回の例では起きなくなりました。そこから先は、読みの速さと書きの手間を測って、わざと崩すかを決めます。

非正規化はしてはいけないのですか

してよい場面があります。読みがとても多く、結合が遅いと測って分かったときに、合計などを写して持ちます。その代わり、写した値を直す処理を 1 か所にまとめ、ときどき元から数え直して食い違いを確かめます。

ボイス・コッド正規形は第 3 正規形と何が違うのですか

ボイス・コッド正規形は、第 3 正規形より厳しく、「ほかの列を決める列は、どれも候補キー (行を 1 つに決められる列の組) でなければならない」とする形です。第 3 正規形でも、候補キーの一部の列が、候補キーでない列から決まる珍しい表では、この条件を満たさないことがあります。さらに先に第 4、第 5 正規形もありますが、この回では扱いません。

正規化すると必ず遅くなるのですか

読むときに結合する手間は増えますが、遅いかどうかは測って決めます。書くときは直す場所が 1 か所で済むので、書きの多い表ではむしろ手間が減ります。4 本目のように、読む手間と書く手間は反対に動きます。

確認クイズ

答えだと思う選択肢を押すと、その場で正誤と理由が開きます。4 問すべてに正しく答えると、この回は修了で、石が緑に変わります (この端末のブラウザに残ります)。間違えた問いは、あとから解き直せます。

1 問目

1 枚の注文の表で、注文 3 を消したら、その注文でしか買われていなかった商品の名前まで表から消えました。これは何と呼ぶ異常ですか。

2 問目

このコードを動かすと何が出ますか。

写しを数える

rows = [("P1", "マウス"),
        ("P1", "マウス"),
        ("P2", "キーボード")]
d = dict(rows)
print(len(rows), len(d))

3 問目

主キーが (注文, 商品) の明細の表に、商品だけで決まる商品名の列があります。この表が満たしていない正規形はどれですか。

4 問目

注文の明細に、商品の今の値段とは別に「買った時の値段」の列を置きました。この列について正しいのはどれですか。

説明課題

同じ値を 2 か所に置くと何が壊れるかを、「更新」「挿入」「削除」「第 3 正規形」という言葉を使って、注文の表の例で 3〜4 文で説明してください。書いたら、下の模範解答を開いて比べてください。

模範解答

自分の説明を書いてから開いて、比べてください。

この回の先の一歩

新しい機能で表や列を足すたびに、その値がほかの表にもう書いてないかを問えると、食い違いの直しに追われる仕事を前もって減らせます。

もっと深く

この回の続きを、ゆめさくのコースの章や道具で深められます。頁の中の確認クイズに答えたあと、手を動かす量を増やしたいときに使ってください。

一次資料

どれも 2026年10月27日に開いて、題と中身を確かめました。

次へ

道 5 データベースと分散の回

  1. 5-1正規化重なりを無くすこの回
  2. 5-2インデックスと B-tree索引が速い理由頁で読む
  3. 5-3トランザクションACID、分離の度合い頁で読む
  4. 5-4分けて置くスケーリング、複製、CAP頁で読む

道 5 の頁へ戻るほかの道を見る