コンピューターサイエンス:アルゴリズム / OS / ネットワーク / DB

第1正規形

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

第 1 正規形

このレッスンで分かること

  • 1NF の定義「全ての属性が原子値」の本当の意味
  • JSON や配列をセルに入れる設計の是非
  • 1NF 違反を直す 2 通りのアプローチ

第1正規形 とは

繰り返し項目を排除し原子値だけで構成する。本レッスンでは、第1正規形 の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

1NF の定義

第 1 正規形 (1NF) とは、テーブルの全カラムが 原子値 (Atomic Value) だけを持つ状態を指します。原子値とは「それ以上分解する必要のない単一の値」のことです。

逆に言えば、1 つのセルに「複数の値が入っている」「内部構造を持つ」状態は 1NF 違反です。具体的には次のような設計が問題視されます。

user_idnamephones
1Alice090-1111-1111, 080-2222-2222
2Bob070-3333-3333

phones セルに 2 つの電話番号がカンマ区切りで入っています。これは 1NF 違反です。

なぜ 1NF 違反がダメか

「カンマで区切ればいいじゃん」と思うかもしれません。実際これで動くこともあります。しかし、次の操作が悲惨になります。

  • 「080 で始まる番号を持つユーザーを検索」LIKE '%080-%' に頼るしかなく、インデックスが効かない
  • 「2 番目の番号だけ更新」SUBSTRING で文字列処理。バグの温床
  • 集計 — 「番号を 1 つだけ持つユーザー」を数えるのに、COMMA の出現回数を数える羽目に
  • 整合性 — 重複番号やフォーマット違反を防げない

SQL クエリ

SELECT * FROM users WHERE phones LIKE '%080-%';

このクエリは全件走査確定です。インデックスを貼っても、LIKE '%...%' だと使えません。

1NF に直す 2 つのアプローチ

diagram (will load when visible)

案 A:別テーブルに切り出す(推奨)

多値属性は別テーブルにします。

SQL クエリ

CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(50) ); CREATE TABLE user_phones ( user_id INT, phone VARCHAR(20), PRIMARY KEY (user_id, phone), FOREIGN KEY (user_id) REFERENCES users(id) );

これで SELECT user_id FROM user_phones WHERE phone LIKE '080-%' がインデックスで動きます。

案 B:行を増やす(非推奨)

users テーブルにそのまま行を増やす方法もあります。

user_idnamephone
1Alice090-1111-1111
1Alice080-2222-2222

name が冗長になり、Alice の名前を変えるとき複数行更新が必要です。これは 2NF 違反に進むので、案 A を選びましょう。

JSON カラムは 1NF 違反か

最近の DB は JSON 型をネイティブサポートしています。これは 1NF 違反でしょうか。

答えは「目的次第」です。

  • 検索・JOIN・集計の対象 にするなら 1NF 違反扱い。別テーブルに切り出す
  • 不定形の付随情報 (ログのメタデータ、設定スナップショット、API レスポンスのキャッシュなど)なら JSON のままで OK

PostgreSQL の JSONB や MySQL の JSON は GIN インデックスや関数インデックスで部分検索もできますが、本格的に検索したいなら正規テーブルが速いです。

複合属性も 1NF 違反

address"東京都渋谷区..." のような 1 文字列で入っている場合、これも厳密には 1NF 違反です。市区町村だけで集計したいなら分割します。

SQL クエリ

CREATE TABLE addresses ( user_id INT PRIMARY KEY, zip CHAR(7), prefecture VARCHAR(10), city VARCHAR(20), street VARCHAR(50) );

ただし「住所は表示するだけ」「全文検索する」なら 1 カラムで十分なこともあります。

トレードオフと現実解

理論を厳格に適用すると、すべてのカラムを最小単位に分解することになります。実務では「検索・更新・集計の単位」を基準に粒度を決めます。例えば日付は yearmonthday に分けず DATE 1 つで持ち、必要なら YEAR(d) 関数で抽出します。

「セルに何か区切り文字が入っていたら 1NF 違反を疑え」が実務の合言葉。

やってみよう

  • 自分のプロジェクトのテーブルから「カンマ区切り」「JSON」「複合文字列」になっている列を 3 つ探す
  • それぞれ「どんな検索が遅くなるか」を 1 文で書き出す
  • 別テーブルに切り出した場合の CREATE TABLE を書く

よくある質問

Q. なぜ正規化するのですか?

A. データの重複を排除し、更新時の不整合(更新異常・挿入異常・削除異常)を防ぐためです。第 3 正規形まで適用すると保守性が大きく上がります。一方で JOIN が増えるため、検索性能と保守性のバランスで非正規化することもあります。

Q. 1NF と 2NF の違いは?

A. 1NF は「各セルが単一値」、2NF は「主キーの一部だけに依存するカラムをテーブル分割」する形です。複合キーの片方だけに紐づく属性は別テーブルに切り出すと 2NF になります。1NF を満たさないと 2NF 以降の議論はできません。

Q. 全部正規化すれば最適ですか?

A. 理想ですが、JOIN が増えてパフォーマンスが落ちる場面では非正規化(カラム複製、計算結果のキャッシュ)も有効です。OLTP は 3NF、レポーティング用 DWH はスタースキーマ(非正規化)と、用途で使い分けるのが現代的なアプローチです。

次のレッスン

次は 第2正規形 で、複合主キーの一部にしか依存しない列(部分関数従属)を別テーブルに切り出す方法を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. 第1正規形 の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. 第1正規形 とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン