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

时间:2014-07-10 12:27:56

标签: sql postgresql plpgsql

我制作了一个小的pl / pgsql脚本来重命名一些序列(添加前缀)并将其架构设置为“public”。但是我不明白为什么但是我的'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"

提前感谢您的宝贵帮助。

3 个答案:

答案 0 :(得分:1)

我终于找到了问题的根源!在我的函数的开头(蒙版部分" [...]"),我有一个循环,它重命名作为参数传递的模式中的表,并将这些表移动到模式' public&#39 ;。此时,表格所拥有的序列存在于'文件中。和'通知'模式会自动移动到公共模式中。

所以,我只需重命名这些模式的序列,而不是移动它们。但是,我并不真正理解为什么序列的'docuprocess'和'公式化'没有以同样的方式移动!

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

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"他的高级帮助和建议。

答案 1 :(得分:0)

RENAME TO之后此行中缺少空格:

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

因此,在第一个不在模式docuprocess中的序列执行sql语句并引发一个中止循环的错误。

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

答案 2 :(得分:0)

命名冲突?

我注意到您没有在{1}} SQL语句列表中对nspname进行表限定:

SELECT

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预先添加变量和参数。像_一样 但是如果你有这样的冲突,你需要在你的SQL语句中是明确的,并且总是表限定不明确的列名。或者,为简单起见,所有列名称。

_nspname

类似案例:

权限?

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

  

您必须拥有序列才能使用select ns.nspname, c.relname from pg_class c ...。改变一个   在序列的模式中,您还必须拥有新模式的ALTER SEQUENCE特权。

应该有错误信息!检查数据库日志......

始终清理标识符

在动态SQL中使用时需要清理标识符:

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;

在这方面,Postgres 8.4在某种程度上受到限制。版本9.1引入了format()。更多细节在这里:

可能是时候开始考虑upgrade to a current version

标识符的最大长度

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