SQLite | ゼロからはじめるSQL、30日で習得するSQLite:データ操作・設計 - Day18 制約

SQL SQLite
スポンサーリンク

Day18 後半

「PRIMARY KEY と NOT NULL を“実務レベルの武器”にする」

前半で、
PRIMARY KEY = 一意+NULL禁止の番号札
NOT NULL = 空っぽ禁止のルール
という土台はできました。

後半では、そこから一歩進んで、

PRIMARY KEY の自動採番(いちいち番号を考えない)
複合 PRIMARY KEY(2列以上で一意にする)
NOT NULL と DEFAULT の組み合わせ
制約エラーの読み方・付き合い方

を、実務寄りの感覚で固めていきます。


PRIMARY KEY の自動採番

「id を毎回自分で決めない世界」

毎回 INSERT のたびに id を自分で決めるのは、正直しんどいです。
しかも、かぶらないように管理するのはもっとしんどい。

そこでよく使われるのが、SQLite の

INTEGER PRIMARY KEY を「自動採番の id」として使うパターンです。

CREATE TABLE users (
  id    INTEGER PRIMARY KEY,
  name  TEXT NOT NULL,
  email TEXT
);
SQL

この状態で、INSERT をこう書きます。

INSERT INTO users (name, email)
VALUES ('山田太郎', 'taro@example.com');
SQL

id を書いていませんが、
SQLite が自動的に 1, 2, 3, … と一意な整数を振ってくれます。

これが「自動採番」の基本的な使い方です。

アプリ側は「id はデータベースに任せる」という前提で動き、
INSERT 後に last_insert_rowid() などで採番された id を取得して使います。

「id を人間が決めない」
「かぶらない番号を DB に任せる」

というのは、実務ではほぼ必須の設計です。


複合 PRIMARY KEY

「1列では一意にならないときに、2列セットで“番号札”にする」

ときどき、「1列だけでは一意にならない」テーブルが出てきます。

例えば、ユーザーごとの設定を保存する user_settings を考えます。

user_id | key        | value
--------+------------+---------
1       | theme      | dark
1       | language   | ja
2       | theme      | light

ここで、「同じ user_id が同じ key を二重に持つのはNG」にしたいとします。

id という単独の PRIMARY KEY を作る方法もありますが、
「user_id と key の組み合わせが一意」というルールの方が自然です。

その場合、複合 PRIMARY KEY を使います。

CREATE TABLE user_settings (
  user_id INTEGER NOT NULL,
  key     TEXT    NOT NULL,
  value   TEXT,
  PRIMARY KEY (user_id, key)
);
SQL

この定義の意味は、

user_id と key のペアが PRIMARY KEY
つまり、この組み合わせはテーブル内で一意
どちらも NULL 禁止

ということです。

これにより、

同じ user_id, key の組み合わせをもう一度 INSERT しようとするとエラー
→ 「同じ設定を二重に持つ」というおかしな状態を防げる

という状態になります。

「何を一意にしたいのか」をちゃんと考えて、
それに合わせて PRIMARY KEY(単独か複合か)を設計するのがポイントです。


NOT NULL と DEFAULT の組み合わせ

「必須だけど、入れ忘れたときの“デフォルト値”を決めておく」

NOT NULL は「NULL 禁止」ですが、
現場では「必須だけど、入れ忘れたときの初期値を決めておきたい」
ということもよくあります。

そこで出てくるのが DEFAULT です。

例えば、userscreated_at を持たせて、
「必須(NOT NULL)かつ、指定がなければ現在時刻を入れる」
というルールにしたいとします。

CREATE TABLE users (
  id          INTEGER PRIMARY KEY,
  name        TEXT    NOT NULL,
  created_at  TEXT    NOT NULL DEFAULT (datetime('now'))
);
SQL

この定義の意味は、

created_at は NULL 禁止(NOT NULL)
INSERT 時に値を指定しなければ、datetime(‘now’) が自動で入る

ということです。

INSERT をこう書いても、

INSERT INTO users (name)
VALUES ('山田太郎');
SQL

created_at には自動的に現在時刻が入ります。

NOT NULL だけだと「入れ忘れたらエラー」で止まりますが、
NOT NULL + DEFAULT にすると、

「必須だけど、入れ忘れたときの“安全な初期値”を決めておける」

という設計になります。


制約エラーの読み方

「怒られたときに、“何を守ってくれたのか”を理解する」

制約を付け始めると、INSERT や UPDATE でエラーが出ることが増えます。
これは「失敗」ではなく、むしろ「守ってくれている」状態です。

代表的なのはこの二つです。

PRIMARY KEY 制約違反
NOT NULL 制約違反

PRIMARY KEY 制約違反は、
「一意であるべき列(または組み合わせ)が、既に存在している値と被った」
という意味です。

NOT NULL 制約違反は、
「NULL 禁止の列に NULL を入れようとした」
という意味です。

エラーが出たときに、

「うわ、エラーだ…」で終わらせず、

「どの制約が、どんな“おかしなデータ”を止めてくれたのか」

を一度言葉にしてみると、
制約の設計がどんどん上手くなります。


セキュリティの視点から見る「制約エラーは味方」

「変なデータが“入ってから気づく”のでは遅い」

セキュリティ的に見ると、
制約は「入力バリデーションの最後の砦」です。

アプリ側のバリデーションがバグっていても、
制約があれば、

同じユーザーを二重に登録しようとした
必須のログ情報が抜けたまま INSERT されそうになった

といったタイミングで、
データベースが「それは受け付けない」と拒否してくれます。

攻撃者が変な値を突っ込もうとしても、
制約があれば「そもそもテーブルに入らない」ことも多いです。

もちろん、制約だけで全てを守れるわけではありませんが、

PRIMARY KEY でダブりを防ぐ
NOT NULL で欠損を防ぐ
DEFAULT で最低限の一貫性を保つ

というのは、
「データの土台を固める」という意味で、とても強い防御線 になります。


小さな練習イメージ

頭の中で、次のようなテーブル定義を考えてみてください。

自動採番の id を持つ users テーブル(name は必須、created_at は現在時刻をデフォルト)。
user_id と key の組み合わせが一意な user_settings テーブル(どちらも NOT NULL)。
ログテーブル login_logs で、id は自動採番、user_id と created_at は NOT NULL。

どれも、

INTEGER PRIMARY KEY(自動採番)
PRIMARY KEY (col1, col2)(複合キー)
NOT NULL + DEFAULT

というパターンの組み合わせで書けるはずです。


Day18 後半のまとめ

INTEGER PRIMARY KEY を使うと、id を自動採番にできて、「番号管理」をデータベースに任せられる。
複合 PRIMARY KEY を使うと、「user_id と key の組み合わせ」のような“ペアとしての一意性”を自然に表現できる。
NOT NULL と DEFAULT を組み合わせると、「必須だけど、入れ忘れたときの安全な初期値」を決めておける。
制約エラーは「データベースが変なデータを守ってくれたサイン」であり、その意味を読み解くことで設計力が上がる。
PRIMARY KEY / NOT NULL / DEFAULT をきちんと設計しておくことは、信頼性だけでなく、セキュリティの土台を固めることにも直結する。

ここまで来たあなたは、
「とりあえず動くテーブル」ではなく、
「おかしなデータが入りにくい、守りの固いテーブル」を設計できる段階に入っています。
この先の UNIQUE や FOREIGN KEY などの制約も、今の感覚の延長線上で、自然に理解できるようになります。

タイトルとURLをコピーしました