第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 違反です。

  1. 全ての行が 主キー で一意に識別できる
  2. 各列の値は スカラー値 であり、配列や繰り返しグループを含まない
  3. 同じ意味の列が 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 化のステップ

diagram (will load when visible)

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_PARTSTRING_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 つができることを確認しましょう。

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

理解度チェック (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');

期待される出力

idtitle
1SQL 入門
2インデックス入門

ヒント

query.sql
query.sql
学習モード
コードの実行結果
データベースを初期化中...