skip to Main Content

I am trying to work out how an auto increment key is represented in Postgres, depending on whether you create it using the SERIAL type or using an IDENTITY.

When I create a table with a SERIAL primary key using this DDL:

CREATE TABLE public.test_change_column (
    id SERIAL PRIMARY KEY NOT NULL
)

I get a sequence called ‘test_change_column_id_seq’ and gives the column id a default value of nextval('test_change_column_id_seq'::regclass)

If I create a table without SERIAL and a primary key using this DDL:

CREATE TABLE public.test_change_column (
    id INTEGER PRIMARY KEY NOT NULL GENERATED ALWAYS AS IDENTITY,
)

The creates the sequence but the default value of id is not set to nextval(‘test_change_column_id_seq’::regclass). Instead the is_identity column is set to "YES".

If you create the column without an autoincrement, you cannot add it at a later stage using the SERIAL:

ALTER TABLE public.test_change_column  ALTER COLUMN id TYPE serial; 

this results in the error "type "serial" does not exist".

Using the IDENTITY method, you can add the auto increment using this DDL:

ALTER TABLE public.test_change_column ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY;

This SQL will show how postgres stores the metadata for the 2 different methods:

SELECT column_name, column_default, is_identity 
FROM information_schema.columns 
WHERE table_name = 'test_change_column';

When it comes removing an auto increment, the auto increment is deleted differently depending on whether you used serial or identity.

If the auto increment was created using the serial type, you have to ALTER COLUMN id DROP DEFAULT. This does not delete the associated sequence table.

If the auto increment was created using IDENTITY, you have to ALTER COLUMN id DROP IDENTITY. This also removes the sequence table.

2

Answers


  1. Changing the column to an identity column (and adding the PK constraint) is enough and the correct thing to do (the use of the serial pseudo type is discouraged)

    You can see that e.g. psql reports this correctly as an identity column:

    arthur=> CREATE TABLE public.test_change_column (id INTEGER NOT NULL);
    CREATE TABLE
    arthur=> d public.test_change_column
             Table "public.test_change_column"
     Column |  Type   | Collation | Nullable | Default
    --------+---------+-----------+----------+---------
     id     | integer |           | not null |
    
    
    arthur=> ALTER TABLE public.test_change_column ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY;
    ALTER TABLE
    arthur=> ALTER TABLE public.test_change_column ADD CONSTRAINT test_change_column_pkey PRIMARY KEY(id);
    ALTER TABLE
    arthur=> d public.test_change_column
                         Table "public.test_change_column"
     Column |  Type   | Collation | Nullable |             Default
    --------+---------+-----------+----------+----------------------------------
     id     | integer |           | not null | generated by default as identity
    Indexes:
        "test_change_column_pkey" PRIMARY KEY, btree (id)
    

    You can also verify that the default is working by inserting a row:

    insert into public.test_change_column default values;
    

    Will create a new row and will increment the id value.

    Such a column will be shown in information_schema.columns with is_identity = 'YES' instead of having a default value.


    I recommend to use generated always as identity so that you can’t accidentally bypass the generation through the sequence (with the effect that the sequence and the values in the table are no longer "in sync").

    Login or Signup to reply.
  2. Create table.

    CREATE TABLE public.test_change_column (
        id INTEGER NOT NULL
    );
    
    

    Alter table in one line:

    ALTER TABLE 
        public.test_change_column ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY, 
        ADD CONSTRAINT test_change_column_pkey PRIMARY KEY(id);
    
    d test_change_column
                         Table "public.test_change_column"
     Column |  Type   | Collation | Nullable |             Default              
    --------+---------+-----------+----------+----------------------------------
     id     | integer |           | not null | generated by default as identity
    Indexes:
        "test_change_column_pkey" PRIMARY KEY, btree (id)
    
    

    An IDENTITY column has a sequence associated with it that is used just like with a serial column. It is just not shown as the DEFAULT. To find underlying sequence:

    select pg_get_serial_sequence ('public.test_change_column', 'id');
          pg_get_serial_sequence      
    ----------------------------------
     public.test_change_column_id_seq
    
    select * from public.test_change_column_id_seq ;
     last_value | log_cnt | is_called 
    ------------+---------+-----------
              1 |       0 | f
    
    

    You can manipulate the above with the IDENTITY commands here ALTER TABLE:

    ALTER [ COLUMN ] column_name ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( sequence_options ) ]

    ALTER [ COLUMN ] column_name { SET GENERATED { ALWAYS | BY DEFAULT } | SET sequence_option | RESTART [ [ WITH ] restart ] } […]

    ALTER [ COLUMN ] column_name DROP IDENTITY [ IF EXISTS ]

    UPDATE

    From here serial type:

    The data types smallserial, serial and bigserial are not true types, but merely a notational convenience for creating unique identifier columns (similar to the AUTO_INCREMENT property supported by some other databases). In the current implementation, specifying:

    CREATE TABLE tablename (
    colname SERIAL
    );

    is equivalent to specifying:

    CREATE SEQUENCE tablename_colname_seq AS integer;

    CREATE TABLE tablename (
    colname integer NOT NULL DEFAULT nextval(‘tablename_colname_seq’)
    );

    ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname;

    Thus, we have created an integer column and arranged for its default values to be assigned from a sequence generator. A NOT NULL constraint is applied to ensure that a null value cannot be inserted. (In most cases you would also want to attach a UNIQUE or PRIMARY KEY constraint to prevent duplicate values from being inserted by accident, but this is not automatic.) Lastly, the sequence is marked as “owned by” the column, so that it will be dropped if the column or table is dropped.

    So if you want to ALTER a table to replicate a serial ‘type’ you will need to do the individual steps above:

    CREATE TABLE public.test_change_column (id INTEGER NOT NULL);
    
    create sequence test_change_column_seq;
    
    
    alter 
        table test_change_column alter COLUMN id 
        set default nextval('test_change_column_seq'), 
    ADD CONSTRAINT test_change_column_pkey PRIMARY KEY(id);
    alter sequence test_change_column_seq owned by test_change_column.id;
    
    d test_change_column
                               Table "public.test_change_column"
     Column |  Type   | Collation | Nullable |                   Default                   
    --------+---------+-----------+----------+---------------------------------------------
     id     | integer |           | not null | nextval('test_change_column_seq'::regclass)
    Indexes:
        "test_change_column_pkey" PRIMARY KEY, btree (id)
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search