順番に読み進めながら学べます

列制約 — NOT NULL / UNIQUE / CHECK / 主キー

この記事は、基礎から複雑なSQL,SQLチューニングまでSQLの実践的なスキルを1からマスターする「SQL入門講座の一部」です。
PRIMARY KEY/NOT NULL/UNIQUE/CHECKの宣言方法をapp_memberなどのテーブルで設計し、わざと制約違反のINSERTを実行してconstraint failedエラーが不正データを弾く挙動を体験します。

列制約とは — 不正なデータを DB 側で防ぐ

列制約(column constraint、列に設定する入力ルール)は、CREATE TABLEの列定義に書く「この列に入れてよい値の条件」です。

本記事では代表的な 4 つ — PRIMARY KEY(主キー。

行を一意に識別)、NOT NULL(NULL を禁止)、UNIQUE(重複を禁止)、CHECK(条件式を満たさない値を禁止)— を扱います。

これまでの記事と違い、データは用意せず、演習で自分でテーブルを作り、わざと制約に違反するINSERTを実行してエラーを体験します。

4 つの列制約の役割
PRIMARY KEY行を一意に識別NULL も重複も不可NOT NULLNULL を禁止値の入力を必須化UNIQUE重複した値を禁止(NULL は許容)CHECK条件式が真でない値を禁止
各制約は「列に入れてよい値」を制限します。違反する INSERT / UPDATE は DB がエラーで弾き、不正なデータがテーブルに入るのを防ぎます。

制約付きのテーブルを宣言する

制約は列定義の型のうしろに続けて書きます。

列名 型 制約 制約 ...の形で、1 列に複数の制約を並べられます。

PRIMARY KEYINTEGER列に付けると、その列が行を一意に識別する主キーになります。

NOT NULLは値の省略を禁止、UNIQUEは他の行と同じ値を禁止、CHECK (条件式)は条件式が真にならない値を禁止します。

下のmemberテーブルは、member_idを主キー、handle(ユーザー名)を NOT NULL かつ UNIQUE、ageに「0 以上」の CHECK を付けた定義です。

-- 制約付きテーブルの定義例(読むだけ)
CREATE TABLE IF NOT EXISTS member (
  member_id INTEGER PRIMARY KEY,        -- 主キー: 一意かつ NULL 不可
  handle    TEXT    NOT NULL UNIQUE,    -- 必須かつ重複禁止
  email     TEXT    UNIQUE,             -- 重複禁止(NULL は許容)
  age       INTEGER CHECK (age >= 0)    -- 0 以上のみ許可
);

-- 制約をすべて満たす行は問題なく入る
INSERT INTO member (member_id, handle, email, age)
VALUES (1, 'alice', 'alice@example.com', 30);

「会員テーブルを、不正データが入らないよう制約付きで設計したい」という要件を想定します。(正しく実行できれば解説が表示されます)

① まずDROP TABLE IF EXISTS app_member;で前回分を消してから、app_memberテーブルを作ってください。列はmember_id(整数・主キー)、handle(テキスト・必須かつ重複禁止)、email(テキスト・重複禁止だが NULL は許容)、age(整数・0 以上のみ)です。

② すべての制約を満たす行を 1 件INSERTしてください(例: member_id 1 / handle ascii の文字列 / email ascii の文字列 / age は 0 以上の整数)。

SELECT * FROM app_member;で 1 行入っていることを確認してください。

SQL エディタ

クエリを実行してください

制約違反はエラーになる — NOT NULL と UNIQUE

制約の役割は「違反した書き込みを拒否する」ことです。

NOT NULL列に NULL(または値を省略)を入れようとするとNOT NULL constraint failedエラー、UNIQUE列・PRIMARY KEY列に既存と同じ値を入れようとするとUNIQUE constraint failedエラーになります。

エラーになったINSERTの行はテーブルに入りません。

制約違反 INSERT の流れ
制約を満たすINSERTDB が受理行が追加される制約に違反するINSERTconstraint failedエラー行は追加されない(不正データを防ぐ)
制約を満たす行は通常どおり入りますが、違反する行は DB がエラーで拒否し、テーブルには追加されません。エラーは「制約が機能している」証拠です。
-- NOT NULL 違反: handle に NULL を入れようとする(読むだけ)
INSERT INTO member (member_id, handle, age) VALUES (2, NULL, 20);
--> Error: NOT NULL constraint failed: member.handle

-- UNIQUE 違反: 既存と同じ handle を入れようとする(読むだけ)
INSERT INTO member (member_id, handle, age) VALUES (3, 'alice', 25);
--> Error: UNIQUE constraint failed: member.handle

handleを必須にした効果を、違反 INSERT で確かめたい」という要件を想定します。この演習はエラーが出れば成功です(制約が正しく不正データを弾いている証拠になります)。

① 冒頭のDELETE FROM app_member;で、実践 1 で作ったapp_memberを空に戻してください(再実行しても同じ結果になるようにするためです)。

② NOT NULL のhandle列にわざとNULLを入れるINSERTを自分で書いてください(例: member_id 1 / handle NULL / email NULL / age 20)。

③ 実行して、NOT NULL constraint failed: app_member.handleのエラーが出ることを確認してください。エラーメッセージの末尾に、どのテーブルのどの列で違反したかが示される点も読んでみてください。

SQL エディタ

クエリを実行してください

handleを重複禁止にした効果を、違反 INSERT で確かめたい」という要件を想定します。この演習もエラーが出れば成功です。

① 冒頭のDELETE FROM app_member;で、実践 1 で作ったapp_memberを空に戻してください。

② 1 行目として、handle'alice'の行を自分でINSERTしてください(例: member_id 1 / handle 'alice' / email 'alice@example.com' / age 30)。

③ 2 行目として、handleが同じく'alice'の別の行を自分でINSERTしてください(member_idemail は変えて構いません)。

④ 実行して、1 件目は正常に入り、2 件目でUNIQUE constraint failed: app_member.handleのエラーが出ることを確認してください。

SQL エディタ

クエリを実行してください

CHECK 制約 — 値の条件を式で表す

CHECK (条件式)は、列に入れてよい値を式で表現する制約です。

CHECK (age >= 0)なら負の年齢を、CHECK (price > 0)なら 0 以下の価格を拒否できます。

CHECK (status IN ('active','inactive'))のように、許可する値の集合を限定する使い方も頻出です。

条件式が真にならない値をINSERT / UPDATEしようとするとCHECK constraint failedエラーになります。

本記事の最後の演習として、CHECK (age >= 0)CHECK (status IN (...))を題材に、CHECK 制約が違反を弾く挙動を体験します。

CHECK 制約の真偽判定
INSERT する値条件式の評価結果age = 30age >= 0→ 真受理行が追加age = -5age >= 0→ 偽CHECKconstraint failedstatus ='banned'status IN('active','inactive')→ 偽CHECKconstraint failed
INSERT する各行について CHECK の条件式を評価します。真なら受理、真でなければ CHECK constraint failed として拒否されます。
-- CHECK で値の範囲・集合を制限する(読むだけ)
CREATE TABLE IF NOT EXISTS product (
  product_id INTEGER PRIMARY KEY,
  price      INTEGER CHECK (price > 0),
  status     TEXT    CHECK (status IN ('active','inactive'))
);

-- price <= 0 は CHECK constraint failed で拒否される
INSERT INTO product (product_id, price, status) VALUES (1, -100, 'active');

ageに 0 以上の CHECK を付けた効果を、違反 INSERT で確かめたい」という要件を想定します。エラーが出れば成功です。

① 冒頭のDELETE FROM app_member;で、実践 1 で作ったapp_memberを空に戻してください。

ageにわざと負の値(例: -5)を入れるINSERTを自分で書いてください。handleは NOT NULL なので NULL 以外の値を入れます。

③ 実行して、CHECK constraint failed: app_member.ageのエラーが出ることを確認してください。ageを 0 以上の値に書き換えれば INSERT が通ることも、余裕があれば試してみてください。

SQL エディタ

クエリを実行してください

「会員の状態列status'active' / 'inactive'の 2 値だけに限定したい」という要件を想定します。本記事の最後の演習で、エラーが出れば成功です。

① まずDROP TABLE IF EXISTS app_member_status;で前回分を消してから、app_member_statusテーブルを自分で作ってください。列はmember_id(整数・主キー)とstatus(テキスト・必須かつ'active'または'inactive'のみ許可)の 2 列です。

② 1 行目として、許可値の'active'(または'inactive')をINSERTしてください。

③ 2 行目として、許可リストに無い'banned'INSERTしてください。

④ 実行して、1 件目は通り、2 件目でCHECK constraint failed: app_member_status.statusのエラーが出ることを確認してください。

SQL エディタ

クエリを実行してください
QUIZ

理解度チェック

まずは1問ずつ答えてみましょう。

Q1handle TEXT NOT NULL UNIQUEと宣言された列について、正しい説明はどれですか。

Q2UNIQUE制約のある列に、すでに存在する値と同じ値をINSERTしようとするとどうなりますか。

Q3「価格は 0 より大きい値だけを許可する」という業務ルールをテーブル定義で表現するのに最も適した制約はどれですか。