PostgreSQL 10 Identity Column gets “null value” when inserting multiple rows with default keyword

dba.stackexchange https://dba.stackexchange.com/questions/194222

Question

I recently upgraded from PostgreSQL 9.5 to PostgreSQL 10. One of the nifty features in PostgreSQL 10 is the new identity column type, an alternative to PostgreSQL' serial pseudo-type. Official documentation for identity column can be found one the CREATE TABLE page.

However, when inserting multiple rows into a table with a GENERATED BY DEFAULT AS IDENTITY column and using the keyword DEFAULT to get the next ID value, the default value is coming back as null.

For example, let's say I have a table

CREATE TABLE test (
  id int GENERATED BY DEFAULT AS IDENTITY,
  t text
);
CREATE TABLE

Inserting a single row with the DEFAULT keyword seems to work fine.

INSERT INTO test (id, t) VALUES (DEFAULT, 'a');
INSERT 0 1

Inserting multiple rows does not.

INSERT INTO test (id, t) VALUES (DEFAULT, 'b'), (DEFAULT, 'c');
ERROR:  null value in column "id" violates not-null constraint
DETAIL:  Failing row contains (null, b).

Inserting multiple rows using an implicit default also works.

INSERT INTO test (t) VALUES ('d'), ('e');
INSERT 0 2

The problem specified above does not appear to be present when using the SERIAL column pseudo-type.

CREATE TABLE test2 (
  id SERIAL,
  t text
);
CREATE TABLE

INSERT INTO test2 (id, t) VALUES (DEFAULT, 'a'), (DEFAULT, 'b');
INSERT 0 2

So my question is: am I missing something? Is the DEFAULT keyword just not expected to work with the new identity column? Or is this a bug?

Was it helpful?

Solution

This is in fact a bug. I verified it. I went to go see if it was filed and it seems it already is. It's not just filed, the commit is there.

You can see their test, exactly like yours

+-- VALUES RTEs
+INSERT INTO itest3 VALUES (DEFAULT, 'a');
+INSERT INTO itest3 VALUES (DEFAULT, 'b'), (DEFAULT, 'c');
+SELECT * FROM itest3;

So just wait, it's there for PostgreSQL 10.2.

Possible work around for PostgreSQL < 10.2

If you absolutely must have this, and using the implicit column isn't acceptable. One easy solution would be to retrieve the sequence with the catalog info function

pg_get_serial_sequence(table_name, column_name) 

Which I believe should work, and to set that as a default.

ALTER TABLE ONLY test
  ALTER COLUMN id
  DEFAULT nextval('seqname');

OTHER TIPS

It's a bug as @Evan discovered, so until it is fixed we have to work around it.

As you probably know, it's easy to work around the case for multiple column inserts where not all values are default — just omit the column(s) where you want the default value inserted entirely, default is the default:

INSERT INTO test (t) VALUES ('b'), ('c');
SELECT * FROM test;
id | t 
-: | :-
 1 | b 
 2 | c 

dbfiddle here

If there are multiple columns and you want them all to be default, you can still use a CTE:

WITH w AS (INSERT INTO test (t) VALUES (DEFAULT) RETURNING t )
INSERT INTO test (t) SELECT w.t FROM w CROSS JOIN generate_series(1, 1);
SELECT * FROM test;
id | t  
-: | :--
 1 | foo
 2 | foo

dbfiddle here

Unfortunately, there is no easy workaround if you need all columns to be populated with default:

For the case when there is only one column and it's a serial, I see no way to use the default.

Licensed under: CC-BY-SA with attribution
Not affiliated with dba.stackexchange
scroll top