Behavior of Serial Type Primary Keys in PostgreSQL
TL;DR
One cause of ERROR: duplicate key value violates unique constraint in PostgreSQL is when the Serial type's numbering does not occur, causing the primary key to become inconsistent.
This issue typically arises when you manually assign values to a primary key.
While solutions to this problem are described in many articles, the underlying behavior itself is not often covered. This article documents what happens with the sequence object in such cases.
Environment
The following docker-compose.yaml is used.
docker-compose.yaml
Verifying Sequence Object Behavior
Table Preparation and Schema Explanation
Create a table for verification. We explicitly assign the Serial type to the primary key.
Verify that the table has been created. You can see the users table and the Serial type's users_id_seq.
Check the schema of the users table. The default value is nextval('users_id_seq'::regclass).
nextval is a Sequence Manipulation function. The official description is as follows:
Advances the sequence object to its next value and returns that value. This is done atomically: even if multiple sessions execute nextval concurrently, each will safely receive a distinct sequence value. If the sequence object has been created with default parameters, successive nextval calls will return successive values beginning with 1. Other behaviors can be obtained by using appropriate parameters in the CREATE SEQUENCE command.
nextval itself is a function that advances the sequence object to the next value and returns it. This is how auto-increment works during INSERT.
In other words, numbering does not occur unless this default is called.
Verifying the Actual Behavior
First, let's INSERT into the initial state and check how the numbering works. We use the currval function to get the current sequence object value.
The currval function returns the current number of the sequence object.
As you can see, the numbering is working correctly (sequence objects are 1-indexed). In this case, no problems occur.
Let's DROP TABLE users; and recreate the table. When we then try to specify the ID explicitly, attempting to get the current sequence object results in an error because no numbering has occurred.
Furthermore, if you then attempt an INSERT without specifying the id, the duplicate key value violates unique constraint error mentioned at the beginning occurs.
Incidentally, numbering still advances even when an INSERT fails. So if you repeat the same operation two more times, it will eventually succeed. This happens because the default nextval('users_id_seq'::regclass) is called each time.
Solution
Let's DROP TABLE users once more and recreate the table.
The point is that the value obtained by currval just needs to be consistent.
So you can solve this by using setval to write the current maximum value of the primary key in the table to the corresponding sequence object.
Tips about setval
Incidentally, setval takes a third argument called is_called. If you set this to False, you can specify the value that nextval will return.
In this case, if you write it as follows, while it cannot be confirmed with currval, the value that will be assigned during the next INSERT is MAX(id) + 1.
The official examples are very clear: