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);