postgresqlauto-incrementplpgsqlalter-tablecolumn-defaults

Alter table add column default and execute the default for each row


I am making a function that adds a id column to a given table, creates a sequence and fills the new columns value. The thing is that the column is created but now I need to fill it with nextval() of the created sequence (1,2,3,4,5...). I don't know how to specify that in the add column sentence.

CREATE OR REPLACE FUNCTION create_id(tabla character varying)
  RETURNS void AS
$BODY$ 
DECLARE

BEGIN

IF NOT EXISTS (SELECT information_schema.columns.column_name FROM information_schema.columns WHERE information_schema.columns.table_name=tabla AND information_schema.columns.column_name='id')
THEN
    EXECUTE 'ALTER TABLE '|| tabla ||' ADD COLUMN id numeric(8,0)';

    IF NOT EXISTS (SELECT relname FROM pg_class WHERE relname='seq_id_'||tabla) 
    THEN
        EXECUTE 'CREATE SEQUENCE seq_id_'||tabla||' INCREMENT 1  MINVALUE 1  MAXVALUE 9223372036854775807  START 1 CACHE 1';
        EXECUTE 'GRANT ALL ON TABLE seq_id_'||tabla||' TO postgres';
        EXECUTE 'ALTER TABLE ONLY '||tabla||' ALTER COLUMN id SET DEFAULT nextval(''seq_id_'||tabla||'''::regclass)';
    END IF; 
END IF;

RETURN;
END;
$BODY$
  LANGUAGE plpgsql;

Solution

  • Your function suffers from a number of series problems. Use this instead:

    CREATE OR REPLACE FUNCTION f_create_id(_tbl text)
      RETURNS void AS
    $func$ 
    DECLARE
       _seq text := _tbl || '_id_seq';
    BEGIN
    
    IF EXISTS (
       SELECT 1 FROM pg_namespace n
       JOIN   pg_class     c ON c.relnamespace = n.oid
       JOIN   pg_attribute a ON a.attrelid = c.oid 
       WHERE  n.nspname = current_schema()  -- default to current schema
       AND    c.relname = _tbl
       AND    a.attname = 'id'
       AND    NOT a.attisdropped)
    THEN
       RAISE EXCEPTION 'Column already exists!'; RETURN;
    END IF;
    
    IF EXISTS (
       SELECT 1 FROM pg_namespace n
       JOIN   pg_class     c ON c.relnamespace = n.oid
       WHERE  n.nspname = current_schema()  -- default to current schema
       AND    c.relname = _seq)
    THEN
       RAISE EXCEPTION 'Sequence already exists!'; RETURN;
    END IF; 
    
    EXECUTE format('CREATE SEQUENCE %I.%I', current_schema(), _seq;    
    EXECUTE format($$ALTER TABLE %I.%I ADD COLUMN id numeric(8,0)
                   DEFAULT nextval('%I'::regclass)$$  -- one statement!
                   , current_schema(), _tbl, _seq);
    
    END
    $func$  LANGUAGE plpgsql;
    

    Major points

    Superior alternative

    Given that, you are much better off with a serial type, making everything much simpler:

    CREATE OR REPLACE FUNCTION f_create_id(_tbl text)
      RETURNS void AS
    $func$ 
    BEGIN
    
    IF EXISTS (
       SELECT 1 FROM pg_namespace n
       JOIN   pg_class     c ON c.relnamespace = n.oid
       JOIN   pg_attribute a ON a.attrelid = c.oid 
       WHERE  n.nspname = current_schema()  -- default to current schema
       AND    c.relname = _tbl
       AND    a.attname = _tbl || '_id'     -- proper column name
       AND    NOT a.attisdropped)
    THEN
       RAISE EXCEPTION 'Column already exists!';
    ELSE
       EXECUTE format('ALTER TABLE %I.%I ADD COLUMN %I serial'
                     , current_schema(), _tbl, _tbl || '_id');
    END IF;
    
    END
    $func$  LANGUAGE plpgsql;