pl/pgsql脚本循环中的控制结构问题
我制作了一个pl/pgsql脚本来重命名一些序列(前缀添加),并将它们的模式设置为“public”。但是我不明白为什么我的'ELSE'指令在循环中只执行一次,这是不符合逻辑的,因为我有许多行'nspname'的值不是'docupaccess':pl/pgsql脚本循环中的控制结构问题,sql,postgresql,plpgsql,Sql,Postgresql,Plpgsql,我制作了一个pl/pgsql脚本来重命名一些序列(前缀添加),并将它们的模式设置为“public”。但是我不明白为什么我的'ELSE'指令在循环中只执行一次,这是不符合逻辑的,因为我有许多行'nspname'的值不是'docupaccess': CREATE OR REPLACE FUNCTION move_schemas_to_public(target_schemas text[]) RETURNS integer AS $procedure$ DECLARE rec RECORD;
CREATE OR REPLACE FUNCTION move_schemas_to_public(target_schemas text[]) RETURNS integer AS $procedure$
DECLARE
rec RECORD;
sql text;
newname text;
nbreq integer := 0;
tabsize integer := array_length(target_schemas, 1);
i integer := 1;
debug boolean := false;
BEGIN
-- [...]
FOR rec in
select nspname, c.relname
from pg_class c
inner join pg_namespace ns
on (c.relnamespace = ns.oid)
where c.relkind = 'S'
and ns.nspname = any(target_schemas)
order by 1, 2
LOOP
IF rec.nspname = 'docuprocess' THEN
newname := rec.relname;
ELSE
-- Why these instructions are executed only once : -----
newname := rec.nspname||'_'||rec.relname;
sql := 'ALTER SEQUENCE '||rec.nspname||'.'||rec.relname||' RENAME TO '||newname;
RAISE NOTICE '%', sql;
IF debug is not true THEN
EXECUTE sql;
END IF;
nbreq := nbreq + 1;
--------------------------------------------------------
END IF;
sql := 'ALTER SEQUENCE '||rec.nspname||'.'||newname||' SET SCHEMA public';
RAISE NOTICE '%', sql;
IF debug is not true THEN
EXECUTE sql;
END IF;
nbreq := nbreq + 1;
END LOOP;
-- [...]
RETURN nbreq;
END;
select move_schemas_to_public(
-- schemas list
ARRAY[
'docufacture',
'docuprocess',
'formulaire',
'notification'
]
);
以下是循环SQL查询的结果:
[nspname];[relname]
"docufacture";"exportdoc_idexportdoc_seq"
"docufacture";"tableau_idcolonne_seq"
"docuprocess";"dp_action_champsdocuged_seq"
"docuprocess";"dp_action_commentaire_seq"
"docuprocess";"dp_action_docuged_seq"
"docuprocess";"dp_action_email_id_seq"
"docuprocess";"dp_action_formulaire_seq"
"docuprocess";"dp_action_id_seq"
"docuprocess";"dp_action_imprimer_id_seq"
"docuprocess";"dp_action_lancer_processus_id_seq"
"docuprocess";"dp_action_lancer_programme_id_seq"
"docuprocess";"dp_action_seq"
"docuprocess";"dp_action_transfert_fichier_id_seq"
"docuprocess";"dp_deroulement_etape_seq"
"docuprocess";"dp_deroulement_processus_seq"
"docuprocess";"dp_etape_seq"
"docuprocess";"dp_indisponibilite_seq"
"docuprocess";"dp_intervenant_seq"
"docuprocess";"dp_processus_seq"
"docuprocess";"dp_type_action_seq"
"formulaire";"champ_id_seq"
"formulaire";"fond_id_seq"
"formulaire";"formulaire_id_seq"
"formulaire";"modele_id_seq"
"notification";"notification_id_seq"
提前感谢您的宝贵帮助。在
重命名为之后,这行中缺少一个空格:
sql := 'ALTER SEQUENCE '||rec.nspname||'.'||rec.relname||' RENAME TO '||newname;
因此,当第一个序列不在schemadocupaccess
中时,执行sql
语句并引发错误,从而中止循环
还请注意,您不必按排序rec
查询,因为您正在评估循环中的记录属性,而不使用符合条件的记录的排序。命名冲突?
我注意到您没有在SQL语句的SELECT
列表中对nspname
进行表限定:
select nspname, c.relname
from pg_class c
inner join pg_namespace ns
on (c.relnamespace = ns.oid)
where c.relkind = 'S'
and ns.nspname = any(target_schemas)
order by 1, 2
类似情况:
特权?
如果这不是问题所在,那么可能是缺少特权
您必须拥有序列才能使用ALTER sequence
。换衣服
序列的架构,您还必须对新架构具有CREATE
权限
应该有一个错误消息!检查数据库日志
始终清理标识符
在动态SQL中使用时,您需要清理标识符:
...
newname := quote_ident(rec.relname);
ELSE
...
newname := quote_ident(rec.nspname||'_'||rec.relname);
sql := 'ALTER SEQUENCE ' || quote_ident(rec.nspname) || '.' || quote_ident(rec.relname)
|| ' RENAME TO ' || newname;
等等——在所有情况下。否则,如果您的任何标识符是非标准的(大小写混合、保留字、空格等),语句就会中断。甚至允许SQL注入。(!)
在生成新名称之前,请小心不要应用quote_ident()
Postgres 8.4在这方面有一定的局限性。版本9.1引入了。详情如下:
也许是时候开始考虑一个新的计划了
标识符的最大长度
最后,您的标识符变得相当长。请记住,典型的最大长度为63字节:
我终于找到了问题的根源!在我的函数(屏蔽部分“[…]”)的开头,我有一个循环,它重命名作为参数传递的模式中的表,并将这些表移动到模式“public”中。此时,“docufacture”和“notification”模式中存在的表所拥有的序列会自动移动到公共模式中
所以,我只需要为这些模式重命名序列,而不是移动它们。然而,我真的不明白为什么“DocupAccess”和“formulaire”的序列没有以相同的方式移动
事实上,如果我尝试在表移位后执行以下请求
ALTER SEQUENCE docufacture.exportdoc_idexportdoc_seq RENAME TO docufacture_exportdoc_idexportdoc_seq
ALTER SEQUENCE exportdoc_idexportdoc_seq SET SCHEMA public;
…我犯了这个错误:
ERROR: relation "docufacture.exportdoc_idexportdoc_seq" does not exist
ERROR: cannot move an owned sequence into another schema
…因为“exportdoc_idexportdoc_seq”已移动到公共架构
如果我尝试在表移位后执行以下请求
ALTER SEQUENCE docufacture.exportdoc_idexportdoc_seq RENAME TO docufacture_exportdoc_idexportdoc_seq
ALTER SEQUENCE exportdoc_idexportdoc_seq SET SCHEMA public;
…我犯了这个错误:
ERROR: relation "docufacture.exportdoc_idexportdoc_seq" does not exist
ERROR: cannot move an owned sequence into another schema
如果有人对此有一些解释,我们将不胜感激。
非常感谢
编辑:
因此,一种解决方案是分3步进行:
- 重命名所有序列
- 移动桌子
- 移动剩余序列
代码如下:
CREATE OR REPLACE FUNCTION move_schemas_to_public(target_schemas text[]) RETURNS integer AS $procedure$
DECLARE
rec RECORD;
sql text;
newname text;
nbreq integer := 0;
tabsize integer := array_length(target_schemas, 1);
i integer := 1;
debug boolean := false;
BEGIN
SET lc_messages TO 'en_US.UTF-8';
-- sequences renamming
FOR rec in
select ns.nspname, c.relname
from pg_class c
inner join pg_namespace ns
on (c.relnamespace = ns.oid)
where c.relkind = 'S'
and ns.nspname = any(target_schemas)
LOOP
IF rec.nspname != 'docuprocess' THEN
newname := quote_ident(rec.nspname||'_'||rec.relname);
sql := 'ALTER SEQUENCE '||quote_ident(rec.nspname)||'.'||quote_ident(rec.relname)||' RENAME TO '||newname;
RAISE NOTICE '%', sql;
IF debug is not true THEN
EXECUTE sql;
END IF;
nbreq := nbreq + 1;
END IF;
END LOOP;
-- END sequences
-- tables
FOR rec in
SELECT table_schema, table_name
from information_schema.tables
where table_type = 'BASE TABLE'
and table_schema = any(target_schemas)
LOOP
IF rec.table_schema = 'docuprocess' THEN
newname := rec.table_name;
ELSE
newname := rec.table_schema||'_'||rec.table_name;
sql := 'ALTER TABLE '||rec.table_schema||'.'||rec.table_name||' RENAME TO '||newname;
RAISE NOTICE '%', sql;
IF debug is not true THEN
EXECUTE sql;
END IF;
nbreq := nbreq + 1;
END IF;
sql := 'ALTER TABLE '||rec.table_schema||'.'||newname||' SET SCHEMA public';
RAISE NOTICE '%', sql;
IF debug is not true THEN
EXECUTE sql;
END IF;
nbreq := nbreq + 1;
END LOOP;
-- END tables
-- remaining sequences shifting
FOR rec in
select ns.nspname, c.relname
from pg_class c
inner join pg_namespace ns
on (c.relnamespace = ns.oid)
where c.relkind = 'S'
and ns.nspname = any(target_schemas)
LOOP
sql := 'ALTER SEQUENCE '||quote_ident(rec.nspname)||'.'||quote_ident(rec.relname)||' SET SCHEMA public';
RAISE NOTICE '%', sql;
IF debug is not true THEN
EXECUTE sql;
END IF;
nbreq := nbreq + 1;
END LOOP;
-- END sequences
-- [...] Move functions, drop empty schemas
RETURN nbreq;
END;
$procedure$
LANGUAGE plpgsql;
select move_schemas_to_public(
-- schemas list
ARRAY[
'docufacture',
'docuprocess',
'formulaire',
'notification'
]
);
最后,我要特别感谢“Erwin Brandstetter”,感谢他提供的高级帮助和建议。我不确定你在问什么,但只有循环执行多次,而IF
/ELSE
分支没有执行。几乎就是这样。事实上,ELSE分支中的指令只执行一次,然后循环中断。然而,我刚刚意识到,当我的'debug'变量固定为true时,指令被正确执行,就好像'EXECUTE'指令打破了循环一样。我没有看到你的Postgres版本,它应该总是包含在这些问题中。此外,更重要的是,您没有包含函数头,它是函数的组成部分。始终张贴完整的功能。标题可能是这里的关键部分。我使用8.4版本(postgreSQL)。好的,我会编辑我的帖子,包括标题。帕特里克,你看得好。可悲的是,这只是我帖子上的一个输入错误…:/我刚刚编辑了它。这是循环中止之前在输出控制台中写的最后一个通知:ALTER SEQUENCE formulaire.formulaire\u found\u id\u seq SET SCHEMApublic@Ant0nin:好的,那么循环中止了?应该有一条错误消息(应该在您的问题中)。可能是您正在运行此任务的角色没有某些模式/序列和BAIL的权限……我想它会中止。我尝试了另一个角色和同样的问题。非常奇怪的是,我没有阻塞错误,脚本一直持续到函数结束,函数的所有其他SQL请求都被执行并工作。我唯一的错误消息是法语,但如果我翻译它,可能类似于“由一行取消的请求组成的结果”。@Ant0nin:要获取英语错误消息,请将SET lc_messages='C'
附加到函数头(或会话中的任何位置)。比如:LANGUAGE plpgsql lc_messages='C'
。谢谢您的建议。学点东西总是好的。顺便说一句,这并不能解决我的问题。@Ant0nin:那么请发布完整的函数。你所描述的闻起来很像上述情况。完成。不过,为了避免混乱,我删除了函数的某些部分。@Ant0nin:我又添加了两个想法。所有序列都归postgres用户所有。即使我使用postgres角色,也不会有任何变化。哈,所以问题隐藏在剪辑部分。某种程度上解释了为什么我们不能确定下来在动态SQL中使用标识符时,请记住始终清理标识符。