UNIQUE制約(一意性制約)とは?重複データを防いでデータベースの品質を守る仕組み

プログラミング・IT

「同じメールアドレスで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 KEYUNIQUE制約
テーブル内の数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_idcourse_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制約が設定された列に登録しようとしました。

対処法

  1. 登録しようとしている値が既に存在するか確認
  2. 既存データを更新するか、別の値を使用する
  3. または、重複チェックをしてから挿入する

例:重複チェックをしてから挿入

-- 重複をチェック
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制約を追加しようとしたが、すでに重複データが存在します。

対処法

  1. 重複データを確認
  2. 重複を解消してから制約を追加

重複データを探す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制約にも挑戦してみてください。信頼性の高いデータベースを実現しましょう!

コメント

タイトルとURLをコピーしました