この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:4
ページ更新者:guest
更新日時:2026-06-11 07:07:02

タイトル: 制約
SEOタイトル: SQL 制約(NOT NULL / UNIQUE / PRIMARY KEY / FOREIGN KEY / CHECK / DEFAULT 完全ガイド)

この記事の要点
  • 制約 (Constraint) はテーブルに格納できる値のルールを宣言し、DB 側でデータ整合性を強制する仕組み
  • 主要 6 種: NOT NULL / UNIQUE / PRIMARY KEY / FOREIGN KEY / CHECK / DEFAULT
  • PRIMARY KEY = NOT NULL + UNIQUE + 1 テーブルに 1 個。FOREIGN KEY は親テーブルとの参照整合性を保証
  • CHECK 制約は MySQL 8.0.16 以降で実効。それ以前は構文は通るが無視される
  • 名前付き制約 (CONSTRAINT 名 ...) で命名すると、エラーメッセージや ALTER TABLE DROP CONSTRAINT で扱いやすい

制約 (Constraint) とは

SQL の制約は、テーブルのカラムに格納できる値のルールを宣言する仕組みです。アプリ側のバリデーションだけに頼ると「別経路から INSERT したらゴミデータが入った」という事故が起きますが、DB 側で制約を貼っておけばどんな経路でも不正データは弾かれます。整合性の最後の砦という位置付けです。

SQL 標準で定義されている主要な制約は次の 6 種類です。

制約意味典型用途
NOT NULLNULL を許さない名前、ステータス等の必須項目
UNIQUE重複を許さない(NULL は許す)メールアドレス、社員番号
PRIMARY KEY主キー(NOT NULL + UNIQUE)テーブルの一意識別子
FOREIGN KEY他テーブルの値しか入れられない注文.顧客ID → 顧客.ID
CHECK任意の式を真にする値だけ許可年齢 >= 0、ステータス IN (...)
DEFAULT未指定時の既定値created_at = NOW()

NOT NULL — 必須項目

カラムに NULL を入れさせない制約です。必須入力項目に貼ります。

CREATE TABLE users (
    id    INT PRIMARY KEY,
    name  VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL
);

INSERT INTO users (id, name) VALUES (1, 'Alice');
-- ERROR: email に NULL を入れようとした

UNIQUE — 重複禁止

カラムの値が重複しないことを保証します。NULL は重複として扱われない(複数の NULL が許される)点が PRIMARY KEY との大きな違いです。

CREATE TABLE users (
    id    INT PRIMARY KEY,
    email VARCHAR(100) UNIQUE
);

-- 複合 UNIQUE (組み合わせで一意)
CREATE TABLE enrollments (
    student_id INT,
    course_id  INT,
    CONSTRAINT uq_enroll UNIQUE (student_id, course_id)
);

PRIMARY KEY — 主キー

テーブルの一意識別子です。中身は NOT NULL + UNIQUE ですが、1 テーブルに 1 つしか定義できないのが UNIQUE との違いです。RDBMS は主キーに自動でクラスタインデックスを作るので検索も高速です。

-- 単一カラム主キー
CREATE TABLE products (
    id   INT PRIMARY KEY,
    name VARCHAR(100)
);

-- 複合主キー
CREATE TABLE order_items (
    order_id INT,
    item_id  INT,
    qty      INT,
    PRIMARY KEY (order_id, item_id)
);

FOREIGN KEY — 参照整合性

他テーブルに存在する値しか入れさせない制約です。「存在しない顧客 ID で注文が作られる」事故を防ぎます。

CREATE TABLE customers (
    id   INT PRIMARY KEY,
    name VARCHAR(50)
);

CREATE TABLE orders (
    id          INT PRIMARY KEY,
    customer_id INT NOT NULL,
    CONSTRAINT fk_orders_customer
        FOREIGN KEY (customer_id) REFERENCES customers(id)
        ON DELETE RESTRICT
        ON UPDATE CASCADE
);

ON DELETE / ON UPDATE で親側削除・更新時の挙動を選べます。

オプション挙動
RESTRICT / NO ACTION子に参照が残っていたら親の削除/更新を拒否(既定)
CASCADE親と一緒に子も削除/更新
SET NULL子の外部キー列を NULL にする(子側 NOT NULL なら不可)
SET DEFAULT子の外部キー列を DEFAULT 値にする

CHECK — 任意条件

カラムの値が満たすべき論理式を直接書ける制約です。MySQL では 8.0.16 から実効、それ以前は構文を受け付けるが無視される点に注意。

CREATE TABLE employees (
    id     INT PRIMARY KEY,
    age    INT CHECK (age >= 18 AND age <= 70),
    status VARCHAR(10) CHECK (status IN ('active','retired','leave'))
);

DEFAULT — 既定値

INSERT 時に値が指定されなかった場合の既定値を定めます。created_at に CURRENT_TIMESTAMP を入れるのが典型。

CREATE TABLE posts (
    id         INT PRIMARY KEY,
    title      VARCHAR(200) NOT NULL,
    status     VARCHAR(10)  NOT NULL DEFAULT 'draft',
    created_at DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP
);

後から追加する — ALTER TABLE

運用中のテーブルに制約を追加するときは ALTER TABLE。名前を付けておくと後で DROP しやすくなります。

-- 追加
ALTER TABLE users
  ADD CONSTRAINT uq_users_email UNIQUE (email);

ALTER TABLE orders
  ADD CONSTRAINT fk_orders_customer
      FOREIGN KEY (customer_id) REFERENCES customers(id);

-- 削除
ALTER TABLE users DROP CONSTRAINT uq_users_email;
ALTER TABLE orders DROP FOREIGN KEY fk_orders_customer;  -- MySQL

よくある質問

Q: アプリ側のバリデーションがあれば DB 制約は不要では?
A: 不要ではありません。バッチ処理、別経路の API、手動 SQL など「アプリを通らない」経路は必ず出てきます。DB 制約は最後の砦として常に貼っておくのが定石です。

Q: 複合 UNIQUE と単一 UNIQUE を両方貼れる?
A: 可能。例えば email 単独で UNIQUE かつ (tenant_id, email) でも UNIQUE のような重複定義も問題ありません。

Q: 外部キーを貼るとパフォーマンスが落ちる?
A: INSERT / UPDATE 時に親テーブル参照のコストが乗りますが、整合性のメリットの方が圧倒的に大きいです。親側カラムにインデックスがあればコストは最小限。