「同じメールアドレスで2つ目のアカウントを作られてしまった」「商品コードが重複して、どちらが正しいか分からなくなった」…。
データベースで重複したデータが入ってしまうと、システムが正しく動作しなくなったり、ユーザーに混乱を招いたりします。
UNIQUE制約(一意性制約)は、こうした重複を防いでくれる強力な仕組みです。
UNIQUE制約を設定すると、「この列には同じ値を2回入れられない」というルールをデータベースが自動的に守ってくれるんです。これにより、データの品質と信頼性が大きく向上します。
この記事では、UNIQUE制約の基本から実践的な使い方まで、初心者の方にも分かりやすく解説していきます。
UNIQUE制約とは?基本を理解しよう

まずは、UNIQUE制約がどういうものなのか見ていきましょう。
UNIQUE制約の概要
UNIQUE制約(一意性制約)は、テーブルの列(カラム)に重複した値が入らないようにするルールです。
一意性(いちいせい)というのは、「唯一無二であること」「重複しないこと」を意味します。
例えば、ユーザーテーブルにUNIQUE制約を設定すると、同じメールアドレスを持つユーザーを2人登録できなくなります。
なぜUNIQUE制約が必要なのか
データベースで重複を防ぐ理由は、いくつかあります。
業務ルールの実現
「メールアドレスは1人1つまで」「商品コードは重複してはいけない」といったビジネスルールを、データベースレベルで強制できます。
データの整合性
重複したデータがあると、「どちらが正しいのか」「どちらを使うべきか」が分からなくなります。
効率的な検索
UNIQUE制約を設定すると、自動的にインデックスが作成されます。これにより、検索パフォーマンスが向上するんです。
アプリケーションコードの簡素化
データベース側で重複チェックをしてくれるので、アプリケーションで複雑なチェック処理を書く必要がありません。
PRIMARY KEYとUNIQUE制約の違い
UNIQUE制約を理解する上で、PRIMARY KEY(主キー)との違いを知っておくことが重要です。
共通点
PRIMARY KEYとUNIQUE制約には、共通点があります。
どちらも重複を許さない
同じ値を2回登録できないという点では同じです。
どちらもインデックスが作成される
検索パフォーマンスが向上します。
違い
しかし、いくつかの重要な違いがあります。
| 項目 | PRIMARY KEY | UNIQUE制約 |
|---|---|---|
| テーブル内の数 | 1つだけ | 複数設定可能 |
| NULLの許可 | 許可しない | 許可する(DBによる) |
| 主な用途 | レコードの識別子 | 重複防止 |
| 外部キーからの参照 | 可能 | 可能(推奨されない) |
使い分けの考え方
PRIMARY KEYを使う場面
テーブル内の各レコードを一意に識別するための列に設定します。通常はidのような列ですね。
UNIQUE制約を使う場面
ビジネスロジック上、重複してはいけない列に設定します。メールアドレス、商品コード、社員番号などが該当します。
実例で比較
ユーザーテーブルの例を見てみましょう。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY, -- 主キー:レコードの識別子
email VARCHAR(100) NOT NULL UNIQUE, -- UNIQUE制約:重複不可
username VARCHAR(50) NOT NULL UNIQUE, -- UNIQUE制約:重複不可
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
この例では:
id:PRIMARY KEYで、各ユーザーを識別email:UNIQUE制約で、同じメールアドレスの重複を防止username:UNIQUE制約で、同じユーザー名の重複を防止
UNIQUE制約の作成方法
それでは、実際にUNIQUE制約を設定する方法を見ていきましょう。
テーブル作成時に列レベルで設定
新しくテーブルを作るときに、列の定義と同時にUNIQUE制約を設定できます。
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
product_code VARCHAR(20) NOT NULL UNIQUE,
product_name VARCHAR(100) NOT NULL,
price DECIMAL(10, 2) NOT NULL
);
product_code列にUNIQUEキーワードを付けるだけで設定完了です。
テーブル作成時にテーブルレベルで設定
テーブルレベルで設定すると、制約に名前を付けられます。
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
product_code VARCHAR(20) NOT NULL,
product_name VARCHAR(100) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
CONSTRAINT uq_products_code UNIQUE (product_code)
);
ポイント
CONSTRAINTの後に制約名を指定(この例ではuq_products_code)UNIQUE (列名)で対象の列を指定
制約名の命名規則は、チームで統一しておくと良いですよ。
既存のテーブルに追加
すでに存在するテーブルに、後からUNIQUE制約を追加することもできます。
ALTER TABLE products
ADD CONSTRAINT uq_products_code UNIQUE (product_code);
ALTER TABLE文を使って、テーブルの構造を変更します。
複数の列にそれぞれUNIQUE制約を設定
複数の列に、それぞれ個別のUNIQUE制約を設定できます。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(100) NOT NULL,
username VARCHAR(50) NOT NULL,
phone VARCHAR(20),
CONSTRAINT uq_users_email UNIQUE (email),
CONSTRAINT uq_users_username UNIQUE (username),
CONSTRAINT uq_users_phone UNIQUE (phone)
);
この例では、email、username、phoneの3つの列に、それぞれ別々のUNIQUE制約を設定しています。
複合UNIQUE制約(複数列の組み合わせ)
複数の列を組み合わせて、UNIQUE制約を設定することもできます。
複合UNIQUE制約とは
複合UNIQUE制約は、複数の列の組み合わせが一意になることを保証します。
個々の列には重複があっても構いませんが、組み合わせとしては重複できません。
設定方法
CREATE TABLE course_enrollments (
id INT AUTO_INCREMENT PRIMARY KEY,
student_id INT NOT NULL,
course_id INT NOT NULL,
enrolled_date DATE NOT NULL,
CONSTRAINT uq_enrollment UNIQUE (student_id, course_id)
);
この例では、student_idとcourse_idの組み合わせが一意になります。
動作の例
以下のようなデータ登録の動作になります。
登録できるパターン
-- 学生1がコースAを受講
INSERT INTO course_enrollments (student_id, course_id, enrolled_date)
VALUES (1, 100, '2024-01-15'); -- OK
-- 学生1がコースBを受講(学生は同じだが、コースが違うのでOK)
INSERT INTO course_enrollments (student_id, course_id, enrolled_date)
VALUES (1, 101, '2024-01-16'); -- OK
-- 学生2がコースAを受講(コースは同じだが、学生が違うのでOK)
INSERT INTO course_enrollments (student_id, course_id, enrolled_date)
VALUES (2, 100, '2024-01-17'); -- OK
登録できないパターン
-- 学生1がコースAを再度受講しようとする
INSERT INTO course_enrollments (student_id, course_id, enrolled_date)
VALUES (1, 100, '2024-01-20'); -- エラー!組み合わせが重複
実用例:座席予約システム
映画館の座席予約を考えてみましょう。
CREATE TABLE seat_reservations (
id INT AUTO_INCREMENT PRIMARY KEY,
show_id INT NOT NULL, -- 上映回
seat_number VARCHAR(10) NOT NULL, -- 座席番号
customer_name VARCHAR(100) NOT NULL,
CONSTRAINT uq_seat_reservation UNIQUE (show_id, seat_number)
);
この設定により、「同じ上映回の同じ座席」を2人に予約できないようになります。
異なる上映回なら同じ座席番号を予約できますし、同じ上映回でも異なる座席なら予約できるんです。
NULLとUNIQUE制約の関係

UNIQUE制約とNULL値の関係は、少し特殊です。
NULLは重複とみなされない
多くのデータベース管理システムでは、NULL値は重複としてカウントされません。
つまり、UNIQUE制約が設定されている列に、複数のNULLを入れることができるんです。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(100) UNIQUE,
phone VARCHAR(20) UNIQUE
);
-- 以下はすべて成功する
INSERT INTO users (email, phone) VALUES ('user1@example.com', '090-1111-1111');
INSERT INTO users (email, phone) VALUES ('user2@example.com', NULL);
INSERT INTO users (email, phone) VALUES ('user3@example.com', NULL); -- OK:NULLは重複扱いされない
INSERT INTO users (email, phone) VALUES (NULL, '090-2222-2222');
INSERT INTO users (email, phone) VALUES (NULL, '090-3333-3333'); -- OK
NULLを許可しない場合
NULLを登録できないようにしたい場合は、NOT NULL制約と組み合わせます。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(100) NOT NULL UNIQUE, -- NULLを許可しない
username VARCHAR(50) NOT NULL UNIQUE
);
これで、emailとusernameには必ず値が必要で、かつ重複もできなくなります。
データベースによる違い
SQL Serverなど一部のデータベースでは、NULLも1つだけしか許可されない設定があります。
使用しているデータベースのドキュメントを確認しておくと良いですよ。
UNIQUE制約の確認方法
設定したUNIQUE制約を確認する方法を見ていきましょう。
MySQLでの確認方法
テーブル構造を表示
SHOW CREATE TABLE products;
テーブルの作成文が表示され、UNIQUE制約の設定も確認できます。
インデックス情報を表示
SHOW INDEX FROM products;
UNIQUE制約が設定されている列には、自動的にユニークインデックスが作成されています。
詳細な制約情報を取得
SELECT
CONSTRAINT_NAME,
TABLE_NAME,
COLUMN_NAME
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE
TABLE_SCHEMA = 'データベース名'
AND TABLE_NAME = 'products'
AND CONSTRAINT_NAME LIKE 'uq_%';
UNIQUE制約の一覧が表示されます。
PostgreSQLでの確認方法
SELECT
tc.constraint_name,
tc.table_name,
kcu.column_name
FROM
information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
WHERE
tc.constraint_type = 'UNIQUE'
AND tc.table_name = 'products';
実用的な確認スクリプト
データベース内のすべてのUNIQUE制約を一覧表示するスクリプトです。
-- MySQLの場合
SELECT
TABLE_NAME AS テーブル名,
CONSTRAINT_NAME AS 制約名,
GROUP_CONCAT(COLUMN_NAME ORDER BY ORDINAL_POSITION) AS 列名
FROM
INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE
TABLE_SCHEMA = 'your_database'
AND CONSTRAINT_NAME LIKE 'uq_%'
GROUP BY
TABLE_NAME, CONSTRAINT_NAME
ORDER BY
TABLE_NAME;
UNIQUE制約の削除・変更方法
UNIQUE制約を削除したり、変更したりする方法です。
MySQLでの削除方法
インデックス名で削除
ALTER TABLE products
DROP INDEX uq_products_code;
MySQLでは、UNIQUE制約はインデックスとして実装されているため、DROP INDEXを使います。
PostgreSQLでの削除方法
制約名で削除
ALTER TABLE products
DROP CONSTRAINT uq_products_code;
PostgreSQLでは、DROP CONSTRAINTを使用します。
制約の変更
UNIQUE制約自体を直接変更することはできません。
設定を変えたい場合は、一度削除してから作り直します。
-- 1. 既存の制約を削除
ALTER TABLE products
DROP INDEX uq_products_code;
-- 2. 新しい設定で制約を作成
ALTER TABLE products
ADD CONSTRAINT uq_products_code_name UNIQUE (product_code, product_name);
複合UNIQUE制約の削除
複合UNIQUE制約も、同じ方法で削除できます。
ALTER TABLE course_enrollments
DROP INDEX uq_enrollment;
よくあるエラーと対処法
UNIQUE制約を使う際によく遭遇するエラーと、その解決方法です。
エラー1:重複データの登録エラー
エラーメッセージ例
Duplicate entry 'user@example.com' for key 'uq_users_email'
原因
すでに存在する値を、UNIQUE制約が設定された列に登録しようとしました。
対処法
- 登録しようとしている値が既に存在するか確認
- 既存データを更新するか、別の値を使用する
- または、重複チェックをしてから挿入する
例:重複チェックをしてから挿入
-- 重複をチェック
SELECT COUNT(*) FROM users WHERE email = 'new@example.com';
-- 0件なら挿入
INSERT INTO users (email, username)
VALUES ('new@example.com', 'newuser');
例:ON DUPLICATE KEY UPDATEを使う(MySQL)
INSERT INTO users (email, username)
VALUES ('user@example.com', 'username')
ON DUPLICATE KEY UPDATE
username = VALUES(username);
これで、重複時は更新、新規なら挿入という処理ができます。
エラー2:既存データに重複があり制約を追加できない
エラーメッセージ例
Duplicate entry '090-1111-1111' for key 'phone'
原因
既存のテーブルにUNIQUE制約を追加しようとしたが、すでに重複データが存在します。
対処法
- 重複データを確認
- 重複を解消してから制約を追加
重複データを探すSQL
-- phoneカラムの重複をチェック
SELECT
phone,
COUNT(*) AS count
FROM
users
WHERE
phone IS NOT NULL
GROUP BY
phone
HAVING
COUNT(*) > 1;
重複を解消する例
-- 重複する古いデータを削除(IDが小さい方を残す)
DELETE t1 FROM users t1
INNER JOIN users t2
WHERE
t1.phone = t2.phone
AND t1.id > t2.id;
-- または、重複データを別の値に更新
UPDATE users
SET phone = NULL
WHERE id IN (重複しているIDのリスト);
-- その後、UNIQUE制約を追加
ALTER TABLE users
ADD CONSTRAINT uq_users_phone UNIQUE (phone);
エラー3:NULL制約違反
エラーメッセージ例
Column 'email' cannot be null
原因
NOT NULLとUNIQUEの両方が設定されている列に、NULLを登録しようとしました。
対処法
必ず値を指定して登録します。
-- 間違い
INSERT INTO users (username) VALUES ('testuser');
-- emailがNOT NULLなのに値がないためエラー
-- 正しい
INSERT INTO users (email, username)
VALUES ('test@example.com', 'testuser');
実践的な使用例

UNIQUE制約の実用的な使い方を見ていきましょう。
ユーザー管理システム
複数の識別子に対してUNIQUE制約を設定します。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(100) NOT NULL UNIQUE,
username VARCHAR(50) NOT NULL UNIQUE,
phone VARCHAR(20) UNIQUE,
social_security_number VARCHAR(20) UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
- email:ログインIDとして使用、重複不可
- username:表示名として使用、重複不可
- phone:オプショナルだがあれば一意
- social_security_number:個人番号、重複不可
商品マスタ
商品コードと商品名の組み合わせに制約を設定します。
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
product_code VARCHAR(20) NOT NULL,
manufacturer_id INT NOT NULL,
product_name VARCHAR(100) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
CONSTRAINT uq_product_code UNIQUE (product_code),
CONSTRAINT uq_product_per_manufacturer
UNIQUE (manufacturer_id, product_name)
);
- product_code:商品コードは一意
- manufacturer_id + product_name:同じメーカーで同じ商品名は不可
予約システム
日時と対象の組み合わせに制約を設定します。
CREATE TABLE meeting_room_reservations (
id INT AUTO_INCREMENT PRIMARY KEY,
room_id INT NOT NULL,
reservation_date DATE NOT NULL,
start_time TIME NOT NULL,
end_time TIME NOT NULL,
user_id INT NOT NULL,
CONSTRAINT uq_room_datetime
UNIQUE (room_id, reservation_date, start_time)
);
同じ会議室の同じ日時に、複数の予約ができないようになります。
SNSのフォロー機能
ユーザー間のフォロー関係に制約を設定します。
CREATE TABLE user_follows (
id INT AUTO_INCREMENT PRIMARY KEY,
follower_id INT NOT NULL,
following_id INT NOT NULL,
followed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_follow_pair UNIQUE (follower_id, following_id)
);
同じユーザーを2回フォローできないようになります。
ベストプラクティス
UNIQUE制約を効果的に使うためのポイントをまとめました。
制約には分かりやすい名前を付ける
制約名を付けておくと、エラーメッセージから問題を特定しやすくなります。
命名規則の例
uq_{テーブル名}_{列名}
uq_{テーブル名}_{列名1}_{列名2}
-- 良い例
CONSTRAINT uq_users_email UNIQUE (email)
CONSTRAINT uq_enrollment UNIQUE (student_id, course_id)
-- 悪い例(名前なし)
UNIQUE (email)
NOT NULL制約との組み合わせを検討
業務ルールに応じて、NOT NULLとの組み合わせを決めましょう。
-- 必須かつ一意
email VARCHAR(100) NOT NULL UNIQUE
-- オプショナルだが一意
phone VARCHAR(20) UNIQUE
パフォーマンスへの影響を考慮
UNIQUE制約は自動的にインデックスを作成するため、検索は速くなりますが、挿入・更新は若干遅くなります。
大量データの一括挿入時は、一時的に制約を無効化することも検討できます。
-- 制約を一時的に無効化(MySQL)
ALTER TABLE products DISABLE KEYS;
-- 大量データを挿入
-- ...
-- 制約を再度有効化
ALTER TABLE products ENABLE KEYS;
ただし、無効化する場合は慎重に行い、必ず再度有効化してください。
ビジネスルールを明確にする
UNIQUE制約を設定する前に、本当に一意である必要があるか確認しましょう。
一意にすべきもの
- メールアドレス
- ユーザー名
- 商品コード
- 注文番号
一意でなくても良いもの
- 氏名(同姓同名がいる)
- 住所(家族で同じ住所)
- 電話番号(家族で共有する場合もある)
複合UNIQUE制約は最小限に
複合UNIQUE制約は便利ですが、多用すると複雑になります。
本当に必要な組み合わせだけに限定しましょう。
エラーハンドリングを実装する
アプリケーション側で、UNIQUE制約違反のエラーを適切にハンドリングしましょう。
// Javaの例
try {
userRepository.save(user);
} catch (DataIntegrityViolationException e) {
if (e.getMessage().contains("uq_users_email")) {
throw new DuplicateEmailException("このメールアドレスは既に使用されています");
}
}
ユーザーに分かりやすいエラーメッセージを表示することが大切です。
まとめ
UNIQUE制約(一意性制約)について解説してきました。
重要なポイント
- UNIQUE制約は、列の値が重複しないことを保証する仕組み
- PRIMARY KEYとの違いは、テーブル内に複数設定でき、NULLを許可できる点
- 単一列だけでなく、複数列の組み合わせにも制約を設定できる
- NULLは重複としてカウントされない(データベースによる)
- テーブル作成時または後から追加できる
- 自動的にインデックスが作成され、検索パフォーマンスが向上
- 制約には分かりやすい名前を付けると管理しやすい
- 重複エラーのハンドリングを適切に実装することが重要
UNIQUE制約を適切に設定すれば、データの品質が向上し、バグの少ないシステムを構築できます。
業務ルールをしっかり分析して、どの列に一意性が必要か見極めましょう。
まずは単純なUNIQUE制約から始めて、必要に応じて複合UNIQUE制約にも挑戦してみてください。信頼性の高いデータベースを実現しましょう!

コメント