SERIAL works with NULL, GENERATED ALWAYS AS IDENTITY not - postgresql

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.

Related

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.

PostgreSQL BIGSERIAL and "duplicate key" on insert

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.

Composite PRIMARY KEY enforces NOT NULL constraints on involved columns

This is one strange, unwanted behavior I encountered in Postgres:
When I create a Postgres table with composite primary keys, it enforces NOT NULL constraint on each column of the composite combination.
For example,
CREATE TABLE distributors (m_id integer, x_id integer, PRIMARY KEY(m_id, x_id));
enforces NOT NULL constraint on columns m_id and x_id, which I don't want!
MySQL doesn't do this. I think Oracle doesn't do it as well.
I understand that PRIMARY KEY enforces UNIQUE and NOT NULL automatically but that makes sense for single-column primary key. In a multi-column primary key table, the uniqueness is determined by the combination.
Is there any simple way of avoiding this behavior of Postgres? When I execute this:
CREATE TABLE distributors (m_id integer, x_id integer);
I do not get any NOT NULL constraints of course. But I would not have a primary key either.
If you need to allow NULL values, use a UNIQUE constraint (or index) instead of a PRIMARY KEY (and add a surrogate PK column - I suggest a serial or IDENTITY column in Postgres 10 or later).
Auto increment table column
A UNIQUE constraint allows columns to be NULL:
CREATE TABLE distributor (
distributor_id GENERATED ALWAYS AS IDENTITY PRIMARY KEY
, m_id integer
, x_id integer
, UNIQUE(m_id, x_id) -- !
-- , CONSTRAINT distributor_my_name_uni UNIQUE (m_id, x_id) -- verbose form
);
The manual:
For the purpose of a unique constraint, null values are not considered equal, unless NULLS NOT DISTINCT is specified.
In your case, you could enter something like (1, NULL) for (m_id, x_id) any number of times without violating the constraint. Postgres never considers two NULL values equal - as per definition in the SQL standard.
If you need to treat NULL values as equal (i.e. "not distinct") to disallow such "duplicates", I see two three (since Postgres 15) options:
0. NULLS NOT DISTINCT
This option was added with Postgres 15 and allows to treat NULL values as "not distinct", so two of them conflict in a unique constraint or index. This is the most convenient option, going forward. The manual:
That means even in the presence of a unique constraint it is possible
to store duplicate rows that contain a null value in at least one of
the constrained columns. This behavior can be changed by adding the
clause NULLS NOT DISTINCT ...
Detailed instructions:
Create unique constraint with null columns
1. Two partial indexes
In addition to the UNIQUE constraint above:
CREATE UNIQUE INDEX dist_m_uni_idx ON distributor (m_id) WHERE x_id IS NULL;
CREATE UNIQUE INDEX dist_x_uni_idx ON distributor (x_id) WHERE m_id IS NULL;
But this gets out of hands quickly with more than two columns that can be NULL. See:
Create unique constraint with null columns
2. A multi-column UNIQUE index on expressions
Instead of the UNIQUE constraint. We need a free default value that is never present in involved columns, like -1. Add CHECK constraints to disallow it:
CREATE TABLE distributor (
distributor serial PRIMARY KEY
, m_id integer
, x_id integer
, CHECK (m_id &lt> -1)
, CHECK (x_id &lt> -1)
);
CREATE UNIQUE INDEX distributor_uni_idx
ON distributor (COALESCE(m_id, -1), COALESCE(x_id, -1));
When you want a polymorphic relation
Your table uses column names that indicate that they are probably references to other tables:
CREATE TABLE distributors (m_id integer, x_id integer);
So I think you probably are trying to model a polymorphic relation to other tables – where a record in your table distributors can refer to one m record xor one x record.
Polymorphic relations are difficult in SQL. The best resource I have seen about this topic is "Modeling Polymorphic Associations in a Relational Database". There, four alternative options are presented, and the recommendation for most cases is called "Exclusive Belongs To", which in your case would lead to a table like this:
CREATE TABLE distributors (
id serial PRIMARY KEY,
m_id integer REFERENCES m,
x_id integer REFERENCES x,
CHECK (
((m_id IS NOT NULL)::integer + (x_id IS NOT NULL)::integer) = 1
)
);
CREATE UNIQUE INDEX ON distributors (m_id) WHERE m_id IS NOT NULL;
CREATE UNIQUE INDEX ON distributors (x_id) WHERE x_id IS NOT NULL;
Like other solutions, this uses a surrogate primary key column because primary keys are enforced to not contain NULL values in the SQL standard.
This solution adds a 4th option to the three in #Erwin Brandstetter's answer for how to avoid the case where "you could enter something like (1, NULL) for (m_id, x_id) any number of times without violating the constraint." Here, that case is excluded by a combination of two measures:
Partial unique indexes on each column individually: two records (1, NULL) and (1, NULL) would not violate the constraint on the second column as NULLs are considered distinct, but they would violate the constraint on the first column (two records with value 1).
Check constraint: The missing piece is preventing multiple (NULL, NULL) records, still allowed because NULLs are considered distinct, and anyway because our partial indexes do not cover them to save space and write events. This is achieved by the CHECK constraint, which prevents any (NULL, NULL) records by making sure that exactly one column is NULL.
There's one difference though: all alternatives in #Erwin Brandstetter's answer allow at least one record (NULL, NULL) and any number of records with no NULL value in any column (like (1, 2)). When modeling a polymorphic relation, you want to disallow such records. That is achieved by the check constraint in the solution above.

Is it a requirement to add NOT NULL when defining a column with IDENTITY(1, 1) key word?

The way I understand it, IDENTITY (1, 1) means that we won't provide any data for that column. Sql Server will, adding each time 1 to the value of the previously created row.
why not this
CREATE TABLE MyTable
(
MyTableID INT IDENTITY(1, 1) PRIMARY KEY
)
but rather this
CREATE TABLE MyTable
(
MyTableID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY
)
Why should I add NOT NULL on the definition of such a column? Is that implies that Sql Server could generate NULL values??
Thanks for helping
It's optional in this case.
Irrespective of the setting of ANSI_NULL_DFLT_ON / ANSI_NULL_DFLT_OFF the column that will be added will not be NULL-able.
You would get an error if you were to try and explicitly set an IDENTITY column (or one participating in a primary key constraint) to allow NULL.
No; when specifying a column as either a PRIMARY KEY (in the definition) or as an IDENTITY, there is no need to specify NOT NULL.

How can I add a 2nd serial integer key column to a table? (postgresql)

I have a table with 8440 records with a natural (string) primary key. Now I just discovered that to support a legacy client, I need the records to have integer keys as well. what's the easiest way to add a serial INT column to this table with a unique constraint and populate it with integer values from 1 to 8440?
Alter the table, add a new not null column of type serial, with a unique key on it.
In Postgres, the serial type is a mere alias for the int type with a default value of nextval(some_sequence), the latter of which is created on the fly.