PostgreSQL BIGSERIAL and "duplicate key" on insert - postgresql

I have a table
CREATE TABLE users (
id BIGSERIAL NOT NULL PRIMARY KEY,
created_at TIMESTAMP DEFAULT NOW()
);
First I run
INSERT INTO users (id) VALUES (1);
After I run
INSERT INTO users (created_at) VALUES ('2016-11-10T09:37:59+00:00');
And I get
ERROR: duplicate key value violates unique constraint "users_pkey"
DETAIL: Key (id)=(1) already exists.
Why id sequence is not incremented when I insert "id" by myself?

That is because the DEFAULT clause only gets evaluated if you either omit the column in the SET clause or insert the special value DEFAULT.
In your first INSERT, the DEFAULT clause is not evaluated, so the sequence is not increased. Your second INSERT uses the DEFAULT clause, the sequence is increased and returns the value 1, which collides with the value explicitly given in the previous INSERT.
Don't mix INSERTs with automatic value creation using sequences and INSERTs that explicitly specify the column. Or if you have to, you sould make sure that the values cannot collide, e.g. by using even numbers for automatically generated values and odd numbers for explicit INSERTs.

Related

SERIAL works with NULL, GENERATED ALWAYS AS IDENTITY not

Postgres 12:
CREATE TABLE l_table (
id INT generated always as identity,
w_id int NOT null references w_table(id),
primary key (w_id, id)
)PARTITION BY LIST (w_id);
CREATE table l1 PARTITION OF l_table FOR VALUES IN (1);
insert into l1 (w_id) values (1);
I'm getting:
ERROR: null value in column "id" violates not-null constraint
If I replace INT generated always as identity with SERIAL it works. This is odd as in another table the generated always as identity works with null. Using default as value does not work either.
GAAI is supposed to be the SQL standard way of replacing SERIAL, even It's the suggested one. What am I missing here?
Thanks.
What am I missing here?
You're trying to insert into the partition table l1 directly, instead of the partitioned l_table. This ignores the identity column on the parent table, tries to insert the default null, and fails the non-null constraint that every identity column has. If you instead do
insert into l_table (w_id) values (1);
it will work and route the inserted row into the right partition.
Using default as value does not work either.
Apparently it's quite hard to do that. How to DEFAULT Partitioned Identity Column? over at dba.SE discusses some workarounds.

INSERT INTO excluding ID column violates primary key uniqueness constraint

I have a Postgres 10.6 table with a serial ID column.
When I attempt to insert into it:
INSERT INTO table (col1, col2) VALUES ('foo', 'bar');
excluding the ID column from the column list, I get:
ERROR: duplicate key value violates unique constraint "customer_invoice_pkey"
Detail: Key (id)=(1234) already exists.
Subsequent runs of the query increment the ID in the error message (1235, 1236 etc.)
How can this be happening?
Having a serial column does not prevent you from inserting rows with an explicit value for id. The sequence value is only a default value that is used when id is not specified in the INSERT statement.
So there must have been some “rogue” inserts of that kind. From PostgreSQL v11 on, you can use identity columns (GENERATED ALWAYS AS IDENTITY) to make overriding the sequence value harder.
You could use the setval function to set the sequence to a value higher than the maximum id in the table to work around the problem.

When does a NOT NULL constraint run and can it wait until the transaction is going to commit?

I have a table being populated during an ETL routine a column at a time.
The mandatory columns (which are foreign keys) are set first and at once, so the initial state of the table is:
key | fkey | a
-------|--------|-------
1 | 1 | null
After processing A values, I insert them using SQL Alchemy with PostgreSQL dialect for a simple upsert:
upsert = sqlalchemy.sql.text("""
INSERT INTO table
(key, a)
VALUES (:key, :a)
ON CONFLICT (key) DO UPDATE SET
a = EXCLUDED.a
""")
But this fails because it apparently tried to insert the fkey value as null.
psycopg2.IntegrityError: null value in column "fkey" violates not-null constraint
DETAIL: Failing row contains (1, null, 0).
Is the syntax really correct? Why is it failing? Does SQLAlchemy has any participation on this error or is it translating PLSQL correctly?
My suspicion is that the constraint checks happen before the CONFLICT resolution triggers, so although it would actually work because fkey is guaranteed to be not null before and won't be overwritten, the constraint check only looks at the tentative insertion and the table constraints.
This is a current documented limitation of PostgreSQL, an area where it breaks the spec.
Currently, only UNIQUE, PRIMARY KEY, REFERENCES (foreign key), and EXCLUDE constraints are affected by this setting. NOT NULL and CHECK constraints are always checked immediately when a row is inserted or modified (not at the end of the statement). Uniqueness and exclusion constraints that have not been declared DEFERRABLE are also checked immediately.
You can't defer the NOT NULL constraint, and it seems you understand the default behavior, seen here.
CREATE TABLE foo ( a int NOT NULL, b int UNIQUE, c int );
INSERT INTO foo (a,b,c) VALUES (1,2,3);
INSERT INTO foo (b,c) VALUES (2,3);
ERROR: null value in column "a" violates not-null constraint
DETAIL: Failing row contains (null, 2, 3).

CONSTRAINT causes error, but despite this, auto_increment is incremented

I create table so:
CREATE TABLE mytable(
name CHARACTER VARYING CONSTRAINT exact_11char CHECK( CHAR_LENGTH(name) = 11 ) ,
age INTEGER
)
Then add id PRIMARY KEY column
ALTER TABLE mytable ADD COLUMN id BIGSERIAL PRIMARY KEY
Then, when trying insert in column name data, which character length isn't 11, happened error from CONSTRAINT.
Ok, but also, id column sequence is incremenmted on each failed attempts.
How to make so: on failed (reason CONSTRAINT) attempts, not increment auto_inceremented column?
postgreSQL version is: 9.2
Since the sequence operations are non-transactional. So there is no simple way exists in PostgreSQL to stop the increment operation on sequence when the corresponding insert fails.
Check the link to create a gapless sequences.
Gapless Sequences for Primary Keys

postgres autoincrement not updated on explicit id inserts

I have the following table in postgres:
CREATE TABLE "test" (
"id" serial NOT NULL PRIMARY KEY,
"value" text
)
I am doing following insertions:
insert into test (id, value) values (1, 'alpha')
insert into test (id, value) values (2, 'beta')
insert into test (value) values ('gamma')
In the first 2 inserts I am explicitly mentioning the id. However the table's auto increment pointer is not updated in this case. Hence in the 3rd insert I get the error:
ERROR: duplicate key value violates unique constraint "test_pkey"
DETAIL: Key (id)=(1) already exists.
I never faced this problem in Mysql in both MyISAM and INNODB engines. Explicit or not, mysql always update autoincrement pointer based on the max row id.
What is the workaround for this problem in postgres? I need it because I want a tighter control for some ids in my table.
UPDATE:
I need it because for some values I need to have a fixed id. For other new entries I dont mind creating new ones.
I think it may be possible by manually incrementing the nextval pointer to max(id) + 1 whenever I am explicitly inserting the ids. But I am not sure how to do that.
That's how it's supposed to work - next_val('test_id_seq') is only called when the system needs a value for this column and you have not provided one. If you provide value no such call is performed and consequently the sequence is not "updated".
You could work around this by manually setting the value of the sequence after your last insert with explicitly provided values:
SELECT setval('test_id_seq', (SELECT MAX(id) from "test"));
The name of the sequence is autogenerated and is always tablename_columnname_seq.
In the recent version of Django, this topic is discussed in the documentation:
Django uses PostgreSQL’s SERIAL data type to store auto-incrementing
primary keys. A SERIAL column is populated with values from a sequence
that keeps track of the next available value. Manually assigning a
value to an auto-incrementing field doesn’t update the field’s
sequence, which might later cause a conflict.
Ref: https://docs.djangoproject.com/en/dev/ref/databases/#manually-specified-autoincrement-pk
There is also management command manage.py sqlsequencereset app_label ... that is able to generate SQL statements for resetting sequences for the given app name(s)
Ref: https://docs.djangoproject.com/en/dev/ref/django-admin/#django-admin-sqlsequencereset
For example these SQL statements were generated by manage.py sqlsequencereset my_app_in_my_project:
BEGIN;
SELECT setval(pg_get_serial_sequence('"my_project_aaa"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "my_project_aaa";
SELECT setval(pg_get_serial_sequence('"my_project_bbb"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "my_project_bbb";
SELECT setval(pg_get_serial_sequence('"my_project_ccc"','id'), coalesce(max("id"), 1), max("id") IS NOT null) FROM "my_project_ccc";
COMMIT;
It can be done automatically using a trigger. This way you are sure that the largest value is always used as the next default value.
CREATE OR REPLACE FUNCTION set_serial_id_seq()
RETURNS trigger AS
$BODY$
BEGIN
EXECUTE (FORMAT('SELECT setval(''%s_%s_seq'', (SELECT MAX(%s) from %s));',
TG_TABLE_NAME,
TG_ARGV[0],
TG_ARGV[0],
TG_TABLE_NAME));
RETURN OLD;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER set_mytable_id_seq
AFTER INSERT OR UPDATE OR DELETE
ON mytable
FOR EACH STATEMENT
EXECUTE PROCEDURE set_serial_id_seq('mytable_id');
The function can be reused for multiple tables. Change "mytable" to the table of interest.
For more info regarding triggers:
https://www.postgresql.org/docs/9.1/plpgsql-trigger.html
https://www.postgresql.org/docs/9.1/sql-createtrigger.html