1

我正在制作一个函数,它将一个 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;
4

1 回答 1

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 列只需要对系统目录进行微小的更改。

  • 您必须定义要在其中创建对象的架构。如果要默认使用当前架构,您仍然必须在对目录(或信息架构)表的查询中考虑这一点。表名仅在与模式名称组合时是唯一的。
    我使用会话信息函数current_schema()来找出当前模式。

  • 使用带有用户输入的动态 SQL 时,必须防止SQL 注入。详细信息:
    表名作为 PostgreSQL 函数参数

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

  • 通常,您不需要. EXECUTE GRANT ALL ON TABLE ... TO postgres如果postgres是超级用户(默认),则该角色拥有所有权限。您可能postgres使所有者. 那会有所作为。

  • 我在两个查询中都使用系统目录,而您在其中一个查询中使用信息模式。我一般不喜欢信息模式。它臃肿的视图很。所提供的信息遵循跨数据库标准,但是在编写 plpgsql 函数时有什么好处,无论如何这些函数 100% 不可移植?

优越的替代品

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

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

鉴于此,你最好使用serialtype,让一切变得更简单:

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;
于 2014-03-29T01:35:11.133 回答