Oracle - 使用 dbms_utility.exec_ddl_statement 的游标未正确执行

Oracle - cursor using dbms_utility.exec_ddl_statement not executing properly

我有一个要求 运行 同时跨多个数据库的 SP,其中一部分是从每个数据库中消除一些重复记录。现在,由于 SP 可以 运行 多次,我已经包含一个备份 table 并且需要什么来 t运行 删除它以防 SP 运行 两次连续。

现在,由于我正在通过 DBLINK 创建 tables,我已经研究过我需要使用 dbms_utility.exec_ddl_statement - 但在这种情况下,即使程序执行了,t运行cate 和 drop 查询似乎什么都不做,因为当我 运行 SP 第二次失败时,它告诉我备份的名称 table 已经在使用中(即使我已经包含了在 CREATE 之前删除执行)。

loop
    fetch v_data into v_database_name;
    exit when v_data%NOTFOUND;
    sql_update := 'BEGIN'
                ||'    EXECUTE IMMEDIATE ''truncate table iSecurity2_dupes_bak'';'
                ||' EXCEPTION'
                ||'    WHEN OTHERS THEN'
                ||'       IF SQLCODE != -942 THEN'
                ||'          RAISE;'
                ||'       END IF;'
                ||' END;';
    execute immediate 'begin dbms_utility.exec_ddl_statement@'||v_database_name||'(:sql_update);  end;' using sql_update;
    commit;
    sql_update := 'BEGIN'
                ||'    EXECUTE IMMEDIATE ''DROP TABLE iSecurity2_dupes_bak'';'
                ||' EXCEPTION'
                ||'    WHEN OTHERS THEN'
                ||'       IF SQLCODE != -942 THEN'
                ||'          RAISE;'
                ||'       END IF;'
                ||' END;';
    execute immediate 'begin dbms_utility.exec_ddl_statement@'||v_database_name||'(:sql_update);  end;' using sql_update;
    commit;
    sql_update := 'create table iSecurity2_dupes_bak as select * from iSecurity2';
    execute immediate 'begin dbms_utility.exec_ddl_statement@'||v_database_name||'(:sql_update);  end;' using sql_update;
commit;
.................

ORA-00955: name is already used by an existing object
ORA-06512: at "SYS.DBMS_UTILITY", line 478
ORA-06512: at line 1
ORA-06512: at "database.procedure_name", line 53
ORA-06512: at line 2

游标的其余部分,包括删除、插入、更新和创建 GLOBAL TEMP tables 似乎工作正常,一切都在执行。如果我手动删除备份 table,即使创建失败也会执行。

我很困惑:(

2016 年 8 月 12 日更新

在@Jon Heller 提供的帮助下,我能够在下面转换我的代码,只要我为 DB_LINK 使用静态名称,它就可以工作。但是当我尝试使用变量时它失败了。 尝试了以下两个版本,但仍然无法到达 运行 但是我一直在修改它们 - 我在这里遗漏了什么吗?

注意:现在,我添加了更改会话,因为没有它,由于 ORA-04062,重新 运行 原始程序一直失败:程序 "cw_drop_table" 的时间戳已更改;

第一个版本

    loop
    fetch v_data into v_database_name;
    exit when v_data%NOTFOUND;
    sql_update := 'alter session set REMOTE_DEPENDENCIES_MODE=SIGNATURE';
    execute immediate 'begin dbms_utility.exec_ddl_statement@'||v_database_name||'(:sql_update);  end;' using sql_update;
    commit;
        begin 
                       dbms_utility.exec_ddl_statement@v_database_name (
                            q'[ 
                            create or replace procedure cw_drop_table is sql_drop varchar2(2000);
                                begin 
                                    sql_drop := 'BEGIN' 
                                        ||'    EXECUTE IMMEDIATE ''DROP TABLE iSecurity2_dupes_bak'';' 
                                        ||' EXCEPTION'
                                        ||'    WHEN OTHERS THEN IF SQLCODE != -942 THEN NULL; END IF; END;';
                                    execute immediate sql_drop;
                                    commit;
                                end; ]' );      
                            execute immediate 'begin cw_drop_table@'||v_database_name||'; end;'; 
        end;
    sql_update := 'create table iSecurity2_dupes_bak as select * from iSecurity2';
    execute immediate 'begin dbms_utility.exec_ddl_statement@'||v_database_name||'(:sql_update);  end;' using sql_update;
    commit;

PLS-00352: Unable to access another database 'V_DATABASE_NAME'
PLS-00201: identifier 'DBMS_UTILITY@V_DATABASE_NAME' must be declared
PL/SQL: Statement ignored

第二版

    loop
        fetch v_data into v_database_name;
        exit when v_data%NOTFOUND;
        sql_update := 'alter session set REMOTE_DEPENDENCIES_MODE=SIGNATURE';
        execute immediate 'begin dbms_utility.exec_ddl_statement@'||v_database_name||'(:sql_update);  end;' using sql_update;
        commit;
        declare v_db_name varchar2(100);
            begin   select v_database_name into v_db_name from dual;                    
                execute immediate   'dbms_utility.exec_ddl_statement@'||v_db_name||' ('
                            ||' q''[ '
                            ||' create or replace procedure cw_drop_table is sql_drop varchar2(2000);'
                            ||'     begin ' 
                            ||'         sql_drop := ''BEGIN'' '
                            ||'             ||''       EXECUTE IMMEDIATE ''DROP TABLE iSecurity2_dupes_bak'';'' '
                            ||'             ||''    EXCEPTION'' '
                            ||'             ||''       WHEN OTHERS THEN IF SQLCODE != -942 THEN NULL; END IF; END;''; '
                            ||'         execute immediate sql_drop;'
                            ||'         commit;'
                            ||'     end; ]'' ); '   
                            ||' execute immediate ''begin cw_drop_table@'||v_db_name||'; end;''; ';
            end;
        sql_update := 'create table iSecurity2_dupes_bak as select * from iSecurity2';
        execute immediate 'begin dbms_utility.exec_ddl_statement@'||v_database_name||'(:sql_update);  end;' using sql_update;
    commit;

PLS-00103: Encountered the symbol "DROP" when expecting one of the following:

* & = - + ; < / > at in is mod remainder not rem
<an exponent (**)> <> or != or ~= >= <= <> and or like LIKE2_
LIKE4_ LIKEC_ between || member SUBMULTISET_

解决方案

经过深思熟虑和淋浴后,我放弃了上述方法并采用了以下方法。不知道为什么我没有早点考虑它:|

注意:如果有人读过这个冗长的问题并知道我在 08/12/2016 更新中做错了什么,我很想知道:)

    loop
        fetch v_data into v_database_name;
        exit when v_data%NOTFOUND;
        sql_update := 'alter session set REMOTE_DEPENDENCIES_MODE=SIGNATURE';
        execute immediate 'begin dbms_utility.exec_ddl_statement@'||v_database_name||'(:sql_update);  end;' using sql_update;
        commit;
        begin       
            sql_update:='DROP TABLE iSecurity2_dupes_bak';
            execute immediate 'begin dbms_utility.exec_ddl_statement@'||v_database_name||'(:sql_update);  end;' using sql_update;

            EXCEPTION
                WHEN OTHERS THEN
                  IF SQLCODE = -942 THEN
                    NULL; -- suppresses ORA-00942 exception
                  ELSE
                     RAISE;
                  END IF;
        END;

DBMS_UTILITY.EXEC_DDL_STATEMENT 仅可靠 运行s DDL。如果您尝试使用 PL/SQL 块 运行 它,它将无声地失败,而不是 运行 任何东西。

这可以通过 运行ning 一个显然应该失败的 PL/SQL 块来证明。下面的代码 应该 生成 ORA-01476: divisor is equal to zero。但它什么都不做。

begin
    dbms_utility.exec_ddl_statement@myself(
        q'[declare v_test number; begin v_test := 1/0; end;]'
    );
end;
/

使用临时程序 运行 PL/SQL 远程阻止。使用 DBMS_UTILITY.EXEC_DDL_STATEMENT 创建过程,然后使用原生动态 SQL.

调用它
begin
    dbms_utility.exec_ddl_statement@myself(
        q'[
            create or replace procedure test_procedure
            is
                v_test number;
            begin
                v_test := 1/0;
            end;
        ]'
    );
    execute immediate 'begin test_procedure@myself; end;';
end;
/

RESULTS:

ORA-01476: divisor is equal to zero
ORA-06512: at "JHELLER.TEST_PROCEDURE", line 5
ORA-06512: at line 1
ORA-06512: at line 12

我认为这种行为是一个错误。 Oracle 应该抛出错误,而不是什么都不做。


欢迎来到连接地狱。当字符串嵌入 4 层深时,字符串会变得混乱。但是您可以做一些事情让生活更轻松:

  1. 使用嵌套替代引用机制。比如q'[ ... ]',里面有一个q'< ... >',等等
  2. 使用多行字符串。无需连接多行,只需使用一个字符串即可。
  3. 使用额外的间距来帮助识别字符串的开头和结尾。当事情变得如此疯狂时,值得将字符串定界符单独放在一行上,这样一切都很容易排列。
  4. 使用 REPLACE 而不是串联。

我使用这些提示重新格式化了您的部分代码。 Whosebug 不理解替代引用机制,但字符串在好的 Oracle SQL 编辑器中应该看起来更好。

declare
    v_db_name varchar2(30) := 'myself';
    sql_update varchar2(32767);
begin
    execute immediate replace(
    q'[
        begin
            dbms_utility.exec_ddl_statement@#DB_NAME#
            (
                q'<
                    create or replace procedure cw_drop_table is
                        sql_drop varchar2(2000);
                    begin
                        sql_drop :=
                        q'{
                            BEGIN
                                EXECUTE IMMEDIATE 'DROP TABLE iSecurity2_dupes_bak';
                            EXCEPTION WHEN OTHERS THEN
                                IF SQLCODE != -942 THEN
                                    NULL;
                                END IF;
                            END;
                        }';
                        execute immediate sql_drop;
                    end;
                >'
            );
            execute immediate 'begin cw_drop_table@#DB_NAME#; end;';
        end;
    ]', '#DB_NAME#', v_db_name);

    sql_update := 'create table iSecurity2_dupes_bak as select * from iSecurity2';
    execute immediate 'begin dbms_utility.exec_ddl_statement@'||v_db_name||
        '(:sql_update);  end;' using sql_update;
    commit;
end;
/