第1正規形 (1NF)
このレッスンで分かること
- 1NF は「1 セル 1 値、繰り返しグループなし」が条件、正規化の出発点
- カンマ区切り (
'sql,db,api') や連番列 (tag1, tag2, tag3) は 1NF 違反- 子テーブルに切り出して縦持ちに変換するのが基本手順
第1正規形 とは
データベース設計の基礎、第一正規形(1NF)について、SQLでの具体例を通して実践的に理解を深めます。
1NF の役割
第1正規形 (First Normal Form, 1NF) は正規化の出発点で、各列の値が単一でこれ以上分解できない状態を求めます。表計算ソフトで「タグ」列にカンマ区切りで複数の値を詰め込むような設計は、ここで弾かれます。1NF は SQL の集計・検索を素直に書けるようにするための前提条件であり、ここを満たさないと 2NF, 3NF の議論に進めません。
1NF の判定基準
テーブルが 1NF を満たすには次の 3 点が必要です。1 つでも欠けると 1NF 違反です。
- 全ての行が
主キーで一意に識別できる - 各列の値は
スカラー値であり、配列や繰り返しグループを含まない - 同じ意味の列が
tag1, tag2, tag3のように連番で並んでいない
1NF 違反の典型例
次は 1NF 違反のテーブルです。tags 列にカンマ区切りで複数の値が入っており、phones 列も同じ問題を抱えています。
SQL クエリ
CREATE TABLE articles_bad (
id INT PRIMARY KEY,
title VARCHAR(200),
tags VARCHAR(500), -- 'sql,database,beginner' のように複数値
authors VARCHAR(500) -- '田中,佐藤' のように複数値
);この構造では「sql タグが付いている記事を探す」だけで LIKE '%sql%' のような曖昧検索が必要になり、sqlite まで誤ヒットしてしまいます。タグ追加・削除も文字列操作で行うことになり、複雑さは指数的に増大します。
| 違反パターン | 例 | 問題 |
|---|---|---|
| カンマ区切り | tags = 'sql,db,api' | LIKE '%sql%' で sqlite 誤ヒット |
| 連番列 | tag1, tag2, tag3 | 拡張不可、追加で ALTER TABLE |
| 配列型 (一部 RDB) | tags TEXT[] | 検索・集計が独自構文に依存 |
| JSON 配列 | tags JSON | インデックス効きにくい (用途による) |
1NF 化のステップ
1NF 化したスキーマ
繰り返しグループ を別テーブルに切り出します。これで tags は親子関係を持つ独立した行になり、検索もインデックスも素直に使えます。
SQL クエリ
CREATE TABLE articles (
id INT PRIMARY KEY,
title VARCHAR(200)
);
CREATE TABLE article_tags (
article_id INT,
tag VARCHAR(50),
PRIMARY KEY (article_id, tag)
);この構造なら、タグでの絞り込みは普通の SQL で書けます。
SQL クエリ
SELECT a.id, a.title
FROM articles a
JOIN article_tags t ON t.article_id = a.id
WHERE t.tag = 'sql';JSON 列は 1NF か
現代の DB は JSON 型を持っており、配列をそのまま保存できます。これは「1NF 違反」と切り捨てるのが正しいかというと、答えはケースバイケースです。
検索やリレーションの起点になるデータは、JSON のままにしておくと結局子テーブルが必要になります。集計・検索しないログや設定値だけを JSON にする、という線引きが現実的です。
注意 「
tag1, tag2, tag3のように列を増やせば 1NF っぽくなる」というのは誤解です。同じ意味の列が連番で並ぶ構造も 1NF 違反です。タグの最大数を決め打つことになり拡張性も失います。
1NF は他の正規形の前提条件です。1NF を満たさないテーブルに対して 2NF や 3NF の議論をすることは原則できません。まずは「セル内の値を単一化する」ところから着手しましょう。
集合演算で考える
1NF を満たすテーブルは、リレーショナル代数の集合演算がそのまま使えます。集合演算は「重複なく、順序なく、要素は単一」が前提なので、1NF を欠いた構造ではそもそも正しく動作しないのです。
SQL クエリ
-- 1NF を満たすからこそ、こうした集計が素直に書ける
SELECT tag, COUNT(*) AS article_count
FROM article_tags
GROUP BY tag
ORDER BY article_count DESC;もし tags がカンマ区切りの 1 列だったら、SPLIT_PART や STRING_TO_TABLE で都度パースする必要があり、インデックスも効きません。
縦持ちと横持ち
1NF 化の本質は「横持ち (wide) を 縦持ち (long) に変換する」ことです。タグや電話番号、スキル、好きな色など「複数の値を持ちたい属性」は、すべて子テーブルとして縦持ちに変換します。
SQL クエリ
-- 横持ち (1NF 違反)
-- user_id | tag1 | tag2 | tag3
-- 1 | a | b | c
-- 縦持ち (1NF を満たす)
-- user_id | tag
-- 1 | a
-- 1 | b
-- 1 | c縦持ちにすると行数は増えますが、SQL の集合演算が素直に使えます。GROUP BY user_id でユーザごとのタグ数も即座に取れますし、新しいタグの追加も INSERT 一発で済みます。
1NF の境界線
判定で迷う典型は、複数値とも見える列が実は「単一の概念」を表しているケースです。例えば住所の state, city, line1 は分割できますが、無理に分けるとアプリ側が組み立てる手間が増えます。値の意味、検索要件、運用コストを総合して決めます。
ここまでの要点 1NF は「1 セル 1 値」「繰り返しグループなし」。カンマ区切り・連番列は典型的な違反。子テーブルへ切り出して縦持ち化。JSON 列は「検索しないログ・設定値」だけに使う線引き。
まとめ
- 1NF は「1 セル 1 値、繰り返しグループなし」が条件
- カンマ区切りや連番列は 1NF 違反の代表例
- 子テーブルに切り出して縦持ちに変換するのが基本手順
- JSON は便利だが、検索したいデータは正規化を優先
次のレッスン
次は 第2正規形 (2NF) です。データベース設計の正規化、第二正規形(2NF)について、SQLでの具体例を通して実践的に理解を深めます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- 1NF の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. 1NF とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
テーブル構造
schema.sql
CREATE TABLE articles_bad (
id INT PRIMARY KEY,
title VARCHAR(200),
tags VARCHAR(500)
);
INSERT INTO articles_bad VALUES
(1, 'SQL 入門', 'sql,database,beginner'),
(2, 'インデックス入門', 'sql,index,performance'),
(3, 'Python 基礎', 'python,beginner'),
(4, 'GO で API 構築', 'go,api,backend');
CREATE TABLE articles (
id INT PRIMARY KEY,
title VARCHAR(200)
);
CREATE TABLE article_tags (
article_id INT,
tag VARCHAR(50),
PRIMARY KEY (article_id, tag)
);
INSERT INTO articles VALUES
(1, 'SQL 入門'),
(2, 'インデックス入門'),
(3, 'Python 基礎'),
(4, 'GO で API 構築');
INSERT INTO article_tags VALUES
(1,'sql'), (1,'database'), (1,'beginner'),
(2,'sql'), (2,'index'), (2,'performance'),
(3,'python'), (3,'beginner'),
(4,'go'), (4,'api'), (4,'backend');期待される出力
| id | title |
|---|---|
| 1 | SQL 入門 |
| 2 | インデックス入門 |