データモデル設計ガイド
第 1 正規形(1NF)— 繰り返しを排除する
CSV・配列・繰り返し列をやめ、各セルを原子的に保つ第 1 正規形。
1NF とは
第 1 正規形(1NF)は、1 つのカラムに複数の値を入れないことを求める最も基本的な正規形です。「各セルは原子的(atomic)」であり、CSV 文字列・配列・繰り返しグループ(tag1, tag2, tag3 のような番号付き列)は許されません。
多値を表現したい場合は別テーブルに行として展開し、元のテーブルの主キーを外部キーで参照させます。
1NF 違反の典型例
記事タグをカンマ区切り文字列で持つと、「特定のタグで絞り込む」クエリが LIKE '%sql%' になり、インデックスが使われず遅くなります。また 1 タグを削除するだけでも文字列操作が必要で、検索・更新の両方が壊れやすい状態になります。
sql
-- NG (1NF 違反)
CREATE TABLE articles (
id INT PRIMARY KEY,
tags TEXT -- 'sql,db,index'
);
-- OK (1NF)
CREATE TABLE articles (
id INT PRIMARY KEY
);
CREATE TABLE article_tags (
article_id INT NOT NULL REFERENCES articles(id),
tag VARCHAR(50) NOT NULL,
PRIMARY KEY (article_id, tag)
);繰り返し列も 1NF 違反
phone1 / phone2 / phone3 のような番号付き繰り返し列も 1NF 違反です。「4 件目を登録したい」「2 件目が空のときの扱い」「全員の番号から検索」など、あらゆる操作が破綻します。
繰り返しは縦持ち(行方向)に展開するのが定石。user_phones (user_id, kind, number) のように種別列で区別します。
例外 — JSON / 配列型はどう扱うか
PostgreSQL の jsonb や text[] のような構造化型は、厳密には 1NF を破っていると見なされることがあります。ただし「検索・集計の対象にならない補助情報」や「スキーマが動的で分割コストが見合わない」場面では実務的に許容されます。
判断軸: その列の中身を WHERE / JOIN / GROUP BY で使うか? 使うなら別テーブルに展開、使わないなら JSON のままでも運用可能、というのが落としどころです。
