qmilch
qmilch

Reputation: 13

How to alter PostgreSQL column with entries to be a nextval id

I have a problem with a really big database with following scheme:

id | date | other columns...

The id column is from type integer. It would be ideal if it where from type integer with a nextval constraint. Many of the id entries have unique id's incremented when they where added.

The problem is all rows added since a specific date have no id and the value is null.

Is it possible to add such constraints to tables with existing values (plus null values) so that the null values are filled with integer id's?

And is this possible without losing the old id's and in the best case with ascending order in relation to the date column?

thanks and greetings

Upvotes: 1

Views: 2846

Answers (1)

user330315
user330315

Reputation:

You need to first update the existing rows with a unique, non-null value:

update the_table
  set id = new_id
from (
  select ctid, 
         (select max(id) from the_table)  + row_number() over (order by date) as new_id
  from the_table
  where id is null
) t
where t.ctid = the_table.ctid;

I am not sure if the order of the IDs is guaranteed using this approach, but it's likely that it does.

Now, that the column doesn't contain any NULL values, we can either change it automatically assign new values.

The next steps depend on whether you want to make this an identity column or simply a column with a default from a sequence (essentially a (discouraged) serial column)

Staying with a "serial"

We need to create a sequence and sync it with the highest value in the column.

create sequence the_table_id_seq;
select setval('the_table_id_seq', max(id))
from the_table;

Then use this for the default and link the sequence to the column.

alter table the_table
  alter id set not null,
  alter id set default nextval('the_table_id_seq') ;
  
alter sequence the_table_id_seq owned by the_table.id;

Using an identity column (recommended)

To make this a proper (recommended) identity column (Postgres 10 and later) you can do it like this:

alter table the_table
  alter id set not null,
  alter id add generated always as identity;

Now adding the identity attribute created a new sequence which we need to sync with the existing values in the column:

select setval(pg_get_serial_sequence('the_table', 'id'), max(id))
from the_table;

Alternatively, you could have manually looked up the current max value and provide that directly when specifying the identity default:

alter table the_table
  alter id set not null,
  alter id add generated always as identity (start with 42);

Upvotes: 4

Related Questions