您的函数存在许多系列问题。改用这个:
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
?更简单、更小、更快。
鉴于此,你最好使用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;