pl/pgsql脚本循环中的控制结构问题

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;

我制作了一个pl/pgsql脚本来重命名一些序列(前缀添加),并将它们的模式设置为“public”。但是我不明白为什么我的'ELSE'指令在循环中只执行一次,这是不符合逻辑的,因为我有许多行'nspname'的值不是'docupaccess':

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;
因此,当第一个序列不在schema
docupaccess
中时,执行
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中使用标识符时,请记住始终清理标识符。