【问题标题】:Issue with a control structure in pl/pgsql script looppl/pgsql 脚本循环中的控制结构问题
【发布时间】:2014-08-31 19:56:06
【问题描述】:

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

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"

提前感谢您的宝贵帮助。

【问题讨论】:

  • 我不确定你在问什么,但只有LOOP body 执行了多次,而IF/ELSE 分支没有。
  • 差不多就是这样。事实上,ELSE 分支中的指令只执行一次,然后循环中断。但是,我刚刚意识到,当我的“调试”变量固定为 true 时,指令会正确执行,就好像“执行”指令会中断循环一样。
  • 我没有看到您的 Postgres 版本,应该始终包含在此类问题中。此外,更重要的是,您没有包含函数头,它是函数的组成部分。始终发布 complete 功能。标题可能是这里的关键部分。
  • 我使用的是 8.4 版本(postgreSQL)。好的,我将编辑我的帖子以包含标题。

标签: sql postgresql plpgsql


【解决方案1】:

我终于找到问题的根源了!在我的函数的开头(屏蔽部分“[...]”),我有一个循环,它重命名作为参数传递的模式中的表,并将这些表移动到模式“公共”。此时,“docufacture”和“notification”模式中存在的表所拥有的序列会自动移动到公共模式中。

所以,我只需要为这些模式重命名序列,而不是移动它们。但是我真的不明白为什么'docuprocess'和'formulaire'的序列没有以相同的方式移动!

确实,如果我在表格移动后尝试执行以下请求...

ALTER SEQUENCE docufacture.exportdoc_idexportdoc_seq RENAME TO docufacture_exportdoc_idexportdoc_seq

...我收到了这个错误:

ERROR:  relation "docufacture.exportdoc_idexportdoc_seq" does not exist

...因为“exportdoc_idexportdoc_seq”已移至公共架构。

如果我在表格移动后尝试执行以下请求...

ALTER SEQUENCE exportdoc_idexportdoc_seq SET SCHEMA public;

...我收到了这个错误:

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”提供的先进帮助和建议。

【讨论】:

  • 哈,所以问题隐藏在剪辑的部分。有点解释,为什么我们无法确定它。 :) 记住在动态 SQL 中使用时始终清理标识符。
【解决方案2】:

命名冲突?

我注意到您没有在 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

虽然WHERE 子句中的那个是表限定的。

您没有提供函数头,但如果有一个 变量函数参数 同名 nspname 则优先。然后,您将在查询结果中获得该变量的常量值,这将解释观察到的行为。

一开始就允许这种命名冲突是个坏主意。我很高兴在变量和参数前面加上_。喜欢_nspname
但是,如果您有这样的冲突,您需要在您的 SQL 语句中是明确的,并且始终对不明确的列名进行表限定。或者,为简单起见,所有列名。

select ns.nspname, c.relname
from pg_class c ...

类似情况:

权限?

如果这不是问题,则可能是缺少权限。 Per pg 8.4 documentation:

您必须拥有序列才能使用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 版引入了format()。更多细节在这里:

也许是时候开始考虑upgrade to a current version了。

标识符的最大长度

最后,您的标识符变得相当长。请记住,典型的最大长度为 63 个字节:

【讨论】:

  • 感谢欧文的建议。学习一些东西总是好的。顺便说一句,它并没有解决我的问题。
  • @Ant0nin:那么请发布您的完整功能。你所描述的非常闻起来像上述情况。
  • 完成。但是,我删除了该功能的某些部分以避免混乱。
  • @Ant0nin:我又添加了两个想法。
  • 所有序列都归 postgres 用户所有。即使我使用 postgres 角色,也没有任何变化。
【解决方案3】:

RENAME TO 后面这一行缺少一个空格:

sql := 'ALTER SEQUENCE '||rec.nspname||'.'||rec.relname||' RENAME TO '||newname;

因此,当第一个序列不在架构 docuprocess 中时,sql 语句将被执行并引发错误,从而中止循环。

另请注意,您不必ORDER BY rec 查询,因为您正在循环中评估记录属性,而不是使用合格记录的顺序。

【讨论】:

  • 很好看的帕特里克。可悲的是,这只是我帖子上的一个错字......:/我刚刚编辑了。
  • 这是循环中止前在输出控制台中写入的最后一条通知:ALTER SEQUENCE formulaire.formulaire_fond_id_seq SET SCHEMA public
  • @Ant0nin:好的,所以循环中止了?应该有一条错误消息(应该在您的问题中)。可能是您正在运行此角色的角色只是对您的某些模式/序列和保释金没有权限...
  • 我想它中止了。我尝试了另一个角色和同样的问题。非常奇怪的是我没有阻塞错误,脚本一直持续到函数结束,函数的所有其他 SQL 请求都执行并工作。我唯一的错误消息是法语,但如果我翻译它可能类似于“请求的结果包括取消了 1 行”。
  • @Ant0nin:要获取英文错误消息,请将SET lc_messages = 'C' 附加到函数头(或会话中的任何位置)。喜欢:LANGUAGE plpgsql lc_messages = 'C'.
猜你喜欢
  • 2015-02-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-07
相关资源
最近更新 更多