更改表添加列默认值并执行每行的默认值

时间:2014-03-28 10:45:21

标签: postgresql auto-increment plpgsql alter-table column-defaults

我正在创建一个函数,它将id列添加到给定的表中,创建一个序列并填充新的列值。问题是列已创建,但现在我需要用创建序列的nextval()填充它(1,2,3,4,5 ...)。我不知道如何在添加列句中指定它。

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;

1 个答案:

答案 0 :(得分:2)

您的功能遇到了许多系列问题。请改用:

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;

重点

  • 如果您在同一ALTER TABLE声明中设置列默认值,则会自动插入值 。请注意,这会对大表的性能产生很大影响,因为每行都必须更新,而添加NULL列只需要对系统目录进行微小的更改。

  • 您必须定义架构才能创建对象。如果您要默认使用当前架构,则仍需在查询中考虑这一点。目录(或信息模式)表。表名仅与模式名称结合使用时唯一 我使用session information functions current_schema()来查找当前架构。

  • 在将动态SQL与用户输入一起使用时,必须防范 SQL注入。详细信息:
    Table name as a PostgreSQL function parameter

  • 如果序列已存在,请不要使用它!您可能会干扰现有对象。

  • 通常情况下,需要EXECUTE GRANT ALL ON TABLE ... TO postgres。如果postgres是超级用户(默认),则该角色无论如何都拥有所有权限。您可能想要postgres 所有者。这会有所作为。

  • 我在两个查询中使用系统目录,而您在其中一个查询中使用信息架构。我通常不是信息模式的粉丝。它的膨胀视图是。所提供的信息遵循跨数据库标准,但是在编写plpgsql函数时有什么好处,这些函数100%不可移植?

优越的选择

  • 我建议使用列名id ,这是一种SQL反模式。请改为使用正确的描述性名称,例如tablename || '_id'

  • 使用numeric(8,0)有什么意义?如果您不想要小数位数,为什么不使用integer?更简单,更小,更快。

考虑到这一点,你使用serial type会好得多,使一切变得更加简单:

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;