MeWrite Docs

ERROR: duplicate key value violates unique constraint

PostgreSQLでユニーク制約違反が発生した際のエラー

概要

PostgreSQLで主キー(PRIMARY KEY)またはユニーク制約(UNIQUE)が設定されたカラムに、既に存在する値を挿入しようとした際に発生するエラーです。

エラーメッセージ

ERROR: duplicate key value violates unique constraint "users_pkey"
DETAIL: Key (id)=(1) already exists.

ERROR: duplicate key value violates unique constraint "users_email_key"
DETAIL: Key (email)=(user@example.com) already exists.

原因

  1. 主キーの重複: 既に存在するIDで挿入しようとしている
  2. ユニーク制約違反: ユニークカラムに重複値を挿入
  3. シーケンスのずれ: SERIALカラムのシーケンスが実データとずれている
  4. 並行トランザクション: 同時に同じ値を挿入
  5. データ移行: インポートデータに重複がある

解決策

1. ON CONFLICT DO NOTHINGを使用

1
2
3
4
-- 重複時は何もしない
INSERT INTO users (email, name)
VALUES ('user@example.com', 'John')
ON CONFLICT (email) DO NOTHING;

2. ON CONFLICT DO UPDATEを使用(UPSERT)

1
2
3
4
5
6
-- 重複時は更新
INSERT INTO users (email, name)
VALUES ('user@example.com', 'John')
ON CONFLICT (email) DO UPDATE SET
    name = EXCLUDED.name,
    updated_at = NOW();

3. 複合キーでのON CONFLICT

1
2
3
4
5
-- 複合ユニークキーの場合
INSERT INTO order_items (order_id, product_id, quantity)
VALUES (1, 100, 2)
ON CONFLICT (order_id, product_id) DO UPDATE SET
    quantity = order_items.quantity + EXCLUDED.quantity;

4. シーケンスをリセット

1
2
3
4
5
6
7
8
-- 現在の最大値を確認
SELECT MAX(id) FROM users;

-- シーケンスを最大値+1にリセット
SELECT setval('users_id_seq', (SELECT MAX(id) FROM users));

-- または強制的に設定
SELECT setval('users_id_seq', 100, true);

5. 挿入前に存在確認

1
2
3
4
5
6
-- NOT EXISTSで確認
INSERT INTO users (email, name)
SELECT 'user@example.com', 'John'
WHERE NOT EXISTS (
    SELECT 1 FROM users WHERE email = 'user@example.com'
);

6. CTEを使用したUPSERT

1
2
3
4
5
6
7
8
9
-- 複雑なUPSERTロジック
WITH upsert AS (
    UPDATE users SET name = 'John Updated'
    WHERE email = 'user@example.com'
    RETURNING *
)
INSERT INTO users (email, name)
SELECT 'user@example.com', 'John'
WHERE NOT EXISTS (SELECT * FROM upsert);

7. Laravelでの対処

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// upsert(Laravel 8+)
User::upsert([
    ['email' => 'user1@example.com', 'name' => 'User 1'],
    ['email' => 'user2@example.com', 'name' => 'User 2'],
], ['email'], ['name']);

// updateOrCreate
$user = User::updateOrCreate(
    ['email' => 'user@example.com'],
    ['name' => 'John']
);

8. Rails/ActiveRecordでの対処

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# find_or_create_by
User.find_or_create_by(email: 'user@example.com') do |user|
  user.name = 'John'
end

# upsert_all(Rails 6+)
User.upsert_all([
  { email: 'user1@example.com', name: 'User 1' },
  { email: 'user2@example.com', name: 'User 2' }
], unique_by: :email)

9. 重複データの調査

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- 重複を確認
SELECT email, COUNT(*) as count
FROM users
GROUP BY email
HAVING COUNT(*) > 1;

-- 重複除去して新テーブルに挿入
INSERT INTO users_clean (email, name)
SELECT DISTINCT ON (email) email, name
FROM users
ORDER BY email, created_at DESC;

トランザクション内での対処

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
BEGIN;

-- ロックを取得して確認
SELECT * FROM users WHERE email = 'user@example.com' FOR UPDATE;

-- 存在しなければ挿入
INSERT INTO users (email, name)
SELECT 'user@example.com', 'John'
WHERE NOT EXISTS (
    SELECT 1 FROM users WHERE email = 'user@example.com'
);

COMMIT;

よくある間違い

  • pg_dumpでリストア時にシーケンスがリセットされない
  • COPYコマンドでインポート時に重複チェックをしない
  • ON CONFLICTで制約名を間違える

関連エラー

参考リンク

PostgreSQL の他のエラー

最終更新: 2025-12-13