※この記事にはアフィリエイト広告(プロモーション)が含まれます。
主キーや一意制約の列に既存と同じ値を入れると、PostgreSQL は次のエラーを返します。
ERROR: duplicate key value violates unique constraint "items_code_key"
DETAIL: Key (code)=(A) already exists.
この記事のログは PostgreSQL 18.6(Ubuntu 26.04 の標準パッケージ)で採りました。表は次のとおりです。
CREATE TABLE items (
id serial PRIMARY KEY,
code text NOT NULL UNIQUE,
name text
);
まず見る(結論)
- 引用符の中が制約名、
DETAILのKey (列)=(値)がぶつかった列と値 - 制約名が
表名_pkeyで、値を指定していないidがぶつかっている → シーケンスが既存の最大値より遅れている。setvalで合わせる - 重複したら何もしない・更新する →
INSERT ... ON CONFLICT - トランザクションの中で起きると、後続の SQL が全部失敗する
制約名から列を引く
制約名を付けずに作ると、主キーは 表名_pkey、一意制約は 表名_列名_key になります。定義は pg_constraint で確かめられます。
SELECT conname, contype, pg_get_constraintdef(oid)
FROM pg_constraint WHERE conrelid = 'items'::regclass;
conname | contype | pg_get_constraintdef
---------------------+---------+----------------------
items_id_not_null | n | NOT NULL id
items_code_not_null | n | NOT NULL code
items_pkey | p | PRIMARY KEY (id)
items_code_key | u | UNIQUE (code)
PostgreSQL 18 では NOT NULL も contype = 'n' の制約として並びます。
id を手で入れるとシーケンスがずれる
データ移行などで id を指定して入れると、シーケンスは進みません。
INSERT INTO items (id, code, name) VALUES (1,'A','a'), (2,'B','b'), (3,'C','c');
SELECT last_value, is_called FROM items_id_seq;
last_value | is_called
------------+-----------
1 | f
この後で id を指定せずに入れると、シーケンスが 1 から値を出すので既存の id とぶつかります。
INSERT INTO items (code, name) VALUES ('D','d');
ERROR: duplicate key value violates unique constraint "items_pkey"
DETAIL: Key (id)=(1) already exists.
同じ INSERT を繰り返すと、Key (id)=(2)、(3) と失敗し、4回目に id=4 で入りました。失敗した INSERT でもシーケンスの値は消費されます。
setval で合わせる
シーケンスを表の最大値に合わせます。シーケンス名は pg_get_serial_sequence で引けます。
SELECT setval(pg_get_serial_sequence('items', 'id'), (SELECT max(id) FROM items));
id=1〜3 を入れた直後にこれを実行すると、次の INSERT は id=4 で入りました。
表が空だと max(id) が NULL になり、合わせられません。空の表で 1 から始めたいときは、第3引数に false を渡します。
SELECT setval(pg_get_serial_sequence('items', 'id'), 1, false);
identity 列も同じ
GENERATED ALWAYS AS IDENTITY の列は、値を指定すると拒否されます。
ERROR: cannot insert a non-DEFAULT value into column "id"
DETAIL: Column "id" is an identity column defined as GENERATED ALWAYS.
HINT: Use OVERRIDING SYSTEM VALUE to override.
OVERRIDING SYSTEM VALUE を付けて id=1 を入れた後は、serial と同じく次の INSERT が Key (id)=(1) で失敗しました。setval(pg_get_serial_sequence(...)) で同じように直ります。
ON CONFLICT
重複したときの扱いを INSERT に書けます。ぶつかる列(または制約名)を指定します。
-- 何もしない(RETURNING は0行)
INSERT INTO items (code, name) VALUES ('A','a3')
ON CONFLICT (code) DO NOTHING RETURNING id;
-- 既存の行を更新する。EXCLUDED は入れようとした行
INSERT INTO items (code, name) VALUES ('A','a4')
ON CONFLICT (code) DO UPDATE SET name = EXCLUDED.name RETURNING id, name;
id | name
----+------
1 | a4
-- 制約名で指定する
INSERT INTO items (code, name) VALUES ('A','a5')
ON CONFLICT ON CONSTRAINT items_code_key DO UPDATE SET name = EXCLUDED.name;
一意制約の無い列を指定するとエラーになります。
INSERT INTO items (code, name) VALUES ('A','a6') ON CONFLICT (name) DO NOTHING;
ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
ON CONFLICT で行が入らなかった場合も、シーケンスは進みます。上の3回の ON CONFLICT の前はシーケンスが 11 で、次に新しい行を入れたときの id は 15 でした。
トランザクションの中で失敗すると後続も失敗する
BEGIN;
INSERT INTO items (code) VALUES ('G');
INSERT INTO items (code) VALUES ('A'); -- 重複
ERROR: duplicate key value violates unique constraint "items_code_key"
INSERT INTO items (code) VALUES ('H');
ERROR: current transaction is aborted, commands ignored until end of transaction block
COMMIT;
ROLLBACK
COMMIT は ROLLBACK として扱われ、失敗する前に入れた G も残りませんでした。失敗しうる INSERT の前に SAVEPOINT を置き、失敗したら ROLLBACK TO SAVEPOINT で戻します。こうすると G と H の両方がコミットされました。
BEGIN;
INSERT INTO items (code) VALUES ('G');
SAVEPOINT s1;
INSERT INTO items (code) VALUES ('A'); -- 重複
ROLLBACK TO s1;
INSERT INTO items (code) VALUES ('H');
COMMIT;