コマンド道場

制約(NOT NULL・UNIQUE・CHECK)の使い方

制約を付けた表が、どんな姿になるか

できた表 renraku

NULL制約
shain_idinteger不可主キー
emailtext不可一意
naiseninteger検査

制約ごとに守るものが違います。email は NULL 不可かつ一意naisen は範囲だけを検査していて NULL は許したままです。NULL を止めるのは NOT NULL の仕事で、UNIQUE にも CHECK にもその働きはありません。

制約は「入れさせない」ための仕組みです。 おかしな値が入ってから直すのではなく、入る前にデータベースが拒否します。アプリ側の検査を忘れても、書き忘れた画面から入っても、最後の砦として効きます。⚠️ ただし UNIQUECHECKNULL は素通しです。ここが取り違えのいちばん多い所です。

この書き方が使えない製品があります そのまま使える: PostgreSQL / MySQL / MariaDB / SQLite / SQL Server

  • Oracle Database では使えませんtext 型の列 / この問題のほかの構文は未確認です

未確認: IBM Db2

製品ごとの対応表(根拠つき)を見る

このページの実行結果と図は、すべて本物のデータベースに同じ SQL を流して得たものです。手で書いた「たぶんこうなる」ではありません。

01何を守るのかで使い分ける

結論: 制約は「守るもの」がそれぞれ違います。 足りない制約は書き足せますが、余計な制約はデータを入れられなくします。

  • NOT NULL値が無いことを許さない
  • UNIQUE同じ値が 2 行以上あることを許さない
  • CHECK決めた条件に反する値を許さない
  • PRIMARY KEY … 行を 1 つに決める。NOT NULLUNIQUE を合わせたもので、1 つの表に 1 つだけ
  • REFERENCES … 相手の表に実在する値だけを許す

主キーと UNIQUE は似ていますが、主キーは表に 1 つだけ、UNIQUE はいくつでも付けられます。社員番号を主キーにして、メールアドレスと社員証番号にそれぞれ UNIQUE を付ける、という形になります。

⚠️ 制約はあとから足すほうが大変です。 すでに入っているデータが条件を満たしていないと、足す操作そのものが失敗します。作るときに決めておくのがいちばん安く済みます。

どこに何を書くか
  1. CREATE TABLE renraku (作る表の名前
  2. shain_id int PRIMARY KEY,★主キーは表に 1 つだけ
  3. email text NOT NULL UNIQUE,★制約は並べて書ける
  4. naisen int CHECK (naisen BETWEEN 1000 AND 9999)★条件は括弧の中に書く
  5. );括弧を閉じて終わり

制約は列のうしろにいくつでも並べて書けます。NOT NULL UNIQUE のように空白で区切るだけで、カンマは要りません。

同じアドレスは 2 行目で拒否される
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');
ERROR: duplicate key value violates unique constraint "renraku_email_key"

「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 つに決める」ものなので、値が無いことがそもそも許されないためです。

UNIQUE の列にも CHECK の列にも NULL が入る
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_idemailnaisen
1NULLNULL
2NULLNULL

2

emailUNIQUE なのに NULL が 2 行入り、naisenCHECK があるのに NULL が入りました。どちらの制約も NULL を見ていません。

⚠️ この書き方は Oracle Database では使えません(text 型の列)。製品ごとの対応表

03制約に名前を付けると、エラー文が読めるようになる

結論: CONSTRAINT 名前 を条件の前に書くと、その名前でエラーが出ます。

名前を付けなければ renraku_naisen_check のような名前が自動で付きます。読めなくはありませんが、意味のある名前を付けておくとエラー文だけで「何の約束を破ったのか」が分かります。運用中に問い合わせを受けたとき、この差が効いてきます。

名前は制約を消すときにも使います。ALTER TABLE 表名 DROP CONSTRAINT 名前 と書くので、自動で付いた名前を毎回調べに行かずに済みます。

⚠️ 名前は表の姿には出てきません。効いてくるのは破ったときのエラー文と、消すときの指定です。

⚠️ 1 つの列に複数の CHECK を付けられます。 条件を AND でつなげて 1 つにするより、CONSTRAINT naisen_no_han_iCONSTRAINT 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);
ERROR: new row for relation "renraku" violates check constraint "naisen_no_han_i"

「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 になります。自分で書く必要はありません。

2 つの列を組にした主キー

できた表 houkoku

NULL制約
shain_idinteger不可主キー
tsukiinteger不可主キー・検査
naiyotext

shain_idtsuki両方に主キーが付いています。これで 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_idtsuki
14
15
24

3

禁じているのは組が同じときだけです。(1, 4)(1, 5) は別の組なので両方入ります。ここでもう一度 (1, 4) を入れようとすると、そこで初めて拒否されます。

⚠️ この書き方は Oracle Database では使えません(text 型の列)。製品ごとの対応表

自分で打ってみる

このページの例と同じデータが入った本物のデータベースを、この場(あなたのブラウザの中)で起動できます。打った SQL がサーバーへ送られることはありません。

押すと初回だけ約 9MB を読み込みます(2 回目以降はキャッシュされます)。

解いてみる

読んだだけでは書けるようになりません。実行結果で採点します。

関連するトピック

  • CREATE TABLE制約を書く場所である CREATE TABLE そのものの説明はこちらです。
  • INSERT制約に反したときに出るエラーを、入れる側から見た説明です。
  • IS NULLUNIQUE と CHECK が NULL を素通しにする理由は、NULL の性質そのものです。
  • UPDATEすでにある行を書き換えるときも、同じ制約が検査されます。

根拠(一次情報)