制約(NOT NULL・UNIQUE・CHECK)の使い方
できた表 renraku
| 列 | 型 | NULL | 制約 |
|---|---|---|---|
| shain_id | integer | 不可 | 主キー |
| text | 不可 | 一意 | |
| naisen | integer | 可 | 検査 |
制約ごとに守るものが違います。email は NULL 不可かつ一意、naisen は範囲だけを検査していて NULL は許したままです。NULL を止めるのは NOT NULL の仕事で、UNIQUE にも CHECK にもその働きはありません。
制約は「入れさせない」ための仕組みです。 おかしな値が入ってから直すのではなく、入る前にデータベースが拒否します。アプリ側の検査を忘れても、書き忘れた画面から入っても、最後の砦として効きます。⚠️ ただし UNIQUE も CHECK も NULL は素通しです。ここが取り違えのいちばん多い所です。
この書き方が使えない製品があります そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server
- Oracle Database では使えません(text 型の列) / この問題のほかの構文は未確認です
未確認: IBM Db2
このページの実行結果と図は、すべて本物のデータベースに同じ SQL を流して得たものです。手で書いた「たぶんこうなる」ではありません。
01何を守るのかで使い分ける
結論: 制約は「守るもの」がそれぞれ違います。 足りない制約は書き足せますが、余計な制約はデータを入れられなくします。
NOT NULL… 値が無いことを許さないUNIQUE… 同じ値が 2 行以上あることを許さないCHECK… 決めた条件に反する値を許さないPRIMARY KEY… 行を 1 つに決める。NOT NULLとUNIQUEを合わせたもので、1 つの表に 1 つだけREFERENCES… 相手の表に実在する値だけを許す
主キーと UNIQUE は似ていますが、主キーは表に 1 つだけ、UNIQUE はいくつでも付けられます。社員番号を主キーにして、メールアドレスと社員証番号にそれぞれ UNIQUE を付ける、という形になります。
⚠️ 制約はあとから足すほうが大変です。 すでに入っているデータが条件を満たしていないと、足す操作そのものが失敗します。作るときに決めておくのがいちばん安く済みます。
CREATE TABLE renraku (作る表の名前shain_id int PRIMARY KEY,★主キーは表に 1 つだけemail text NOT NULL UNIQUE,★制約は並べて書けるnaisen int CHECK (naisen BETWEEN 1000 AND 9999)★条件は括弧の中に書く);括弧を閉じて終わり
制約は列のうしろにいくつでも並べて書けます。NOT NULL UNIQUE のように空白で区切るだけで、カンマは要りません。
CREATE TABLE renraku (
shain_id int PRIMARY KEY,
email text NOT NULL UNIQUE
);
INSERT INTO renraku (shain_id, email) VALUES (1, 'sato@example.com');
INSERT INTO renraku (shain_id, email) VALUES (2, 'sato@example.com');「duplicate key value violates unique constraint」で落ちます。1 行目は入っています——制約は行ごとに検査されるので、通る行はそのまま入り、破る行だけが止まります。
⚠️ この書き方は Oracle Database では使えません(text 型の列)。製品ごとの対応表
02⚠️ よくある間違い:UNIQUE も CHECK も NULL を止めない
結論: NULL を止めるのは NOT NULL だけです。
UNIQUE を付けた列に NULL は何行でも入ります。NULL は「値が無い」ことなので、NULL どうしは等しいとみなされないためです。「まだ登録していない人が 100 人いる」という状態を許してしまいます。
CHECK も同じで、条件が NULL になる行は通ったものとして扱われます。条件が真でも偽でもないためです。CHECK (naisen BETWEEN 1000 AND 9999) を付けた列に NULL を入れると、そのまま入ります。
「必ずあって、しかも重複しない」「必ずあって、しかも範囲内」を求めるなら、NOT NULL を一緒に書いてください。 制約を 1 つ書いただけで守れたつもりになるのが、いちばん多い取り違えです。
⚠️ 主キーだけは例外で、NOT NULL を書かなくても NULL を許しません。主キーは「行を 1 つに決める」ものなので、値が無いことがそもそも許されないためです。
CREATE TABLE renraku (
shain_id int PRIMARY KEY,
email text UNIQUE,
naisen int CHECK (naisen BETWEEN 1000 AND 9999)
);
INSERT INTO renraku (shain_id) VALUES (1);
INSERT INTO renraku (shain_id) VALUES (2);
SELECT shain_id, email, naisen FROM renraku ORDER BY shain_id;| shain_id | naisen | |
|---|---|---|
| 1 | NULL | NULL |
| 2 | NULL | NULL |
(2 行)
email は UNIQUE なのに NULL が 2 行入り、naisen は CHECK があるのに NULL が入りました。どちらの制約も NULL を見ていません。
⚠️ この書き方は Oracle Database では使えません(text 型の列)。製品ごとの対応表
03制約に名前を付けると、エラー文が読めるようになる
結論: CONSTRAINT 名前 を条件の前に書くと、その名前でエラーが出ます。
名前を付けなければ renraku_naisen_check のような名前が自動で付きます。読めなくはありませんが、意味のある名前を付けておくとエラー文だけで「何の約束を破ったのか」が分かります。運用中に問い合わせを受けたとき、この差が効いてきます。
名前は制約を消すときにも使います。ALTER TABLE 表名 DROP CONSTRAINT 名前 と書くので、自動で付いた名前を毎回調べに行かずに済みます。
⚠️ 名前は表の姿には出てきません。効いてくるのは破ったときのエラー文と、消すときの指定です。
⚠️ 1 つの列に複数の CHECK を付けられます。 条件を AND でつなげて 1 つにするより、CONSTRAINT naisen_no_han_i と CONSTRAINT naisen_wa_suji のように分けて名前を付けたほうが、どちらに引っかかったのかが分かります。
CREATE TABLE renraku (
shain_id int PRIMARY KEY,
naisen int CONSTRAINT naisen_no_han_i CHECK (naisen BETWEEN 1000 AND 9999)
);
INSERT INTO renraku (shain_id, naisen) VALUES (1, 99);「violates check constraint "naisen_no_han_i"」と出ます。名前を付けていなければ renraku_naisen_check になっていました。エラー文は利用者ではなく、直す人のための情報です。
04複数の列にまたがる制約
結論: 列を並べたあとに書くと、複数の列をまとめた 1 つの制約になります。
PRIMARY KEY (shain_id, tsuki) と書けば、「同じ社員が同じ月に 2 回」だけを禁じられます。shain_id だけの主キーでは社員が一生に 1 回しか登録できず、tsuki だけならその月に登録できるのは 1 人だけになってしまいます。組にして初めて言いたいことが言えます。
UNIQUE (列, 列) も同じ形で書けます。主キーは表に 1 つだけなので、2 つ目以降の「この組は 1 回だけ」は UNIQUE で表します。
CHECK も 2 つ以上の列を使えます。CHECK (owari >= hajime) のように、列どうしの関係を約束にできるのは CHECK だけの働きです。
⚠️ 組にした主キーの列は、全部が自動で NOT NULL になります。自分で書く必要はありません。
できた表 houkoku
| 列 | 型 | NULL | 制約 |
|---|---|---|---|
| shain_id | integer | 不可 | 主キー |
| tsuki | integer | 不可 | 主キー・検査 |
| naiyo | text | 可 |
shain_id と tsuki の両方に主キーが付いています。これで 1 つの主キーです。どちらも NOT NULL と書いていないのに NULL 不可になっています。
CREATE TABLE houkoku (
shain_id int,
tsuki int CHECK (tsuki BETWEEN 1 AND 12),
naiyo text,
PRIMARY KEY (shain_id, tsuki)
);
INSERT INTO houkoku (shain_id, tsuki) VALUES (1, 4);
INSERT INTO houkoku (shain_id, tsuki) VALUES (1, 5);
INSERT INTO houkoku (shain_id, tsuki) VALUES (2, 4);
SELECT shain_id, tsuki FROM houkoku ORDER BY shain_id, tsuki;| shain_id | tsuki |
|---|---|
| 1 | 4 |
| 1 | 5 |
| 2 | 4 |
(3 行)
禁じているのは組が同じときだけです。(1, 4) と (1, 5) は別の組なので両方入ります。ここでもう一度 (1, 4) を入れようとすると、そこで初めて拒否されます。
⚠️ この書き方は Oracle Database では使えません(text 型の列)。製品ごとの対応表
自分で打ってみる
このページの例と同じデータが入った本物のデータベースを、この場(あなたのブラウザの中)で起動できます。打った SQL がサーバーへ送られることはありません。
押すと初回だけ約 9MB を読み込みます(2 回目以降はキャッシュされます)。
解いてみる
読んだだけでは書けるようになりません。実行結果で採点します。
関連するトピック
- CREATE TABLE制約を書く場所である CREATE TABLE そのものの説明はこちらです。
- INSERT制約に反したときに出るエラーを、入れる側から見た説明です。
- IS NULLUNIQUE と CHECK が NULL を素通しにする理由は、NULL の性質そのものです。
- UPDATEすでにある行を書き換えるときも、同じ制約が検査されます。
根拠(一次情報)
- PostgreSQL 18 マニュアル: 制約(NOT NULL・UNIQUE・CHECK・主キー・外部キー)一次情報・確認 2026-09-02
- PostgreSQL 18 マニュアル: CREATE TABLE一次情報・確認 2026-09-02
- PostgreSQL 18 マニュアル: ALTER TABLE一次情報・確認 2026-09-02