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 层深时,字符串会变得混乱。但是您可以做一些事情让生活更轻松:
- 使用嵌套替代引用机制。比如
q'[ ... ]'
,里面有一个q'< ... >'
,等等
- 使用多行字符串。无需连接多行,只需使用一个字符串即可。
- 使用额外的间距来帮助识别字符串的开头和结尾。当事情变得如此疯狂时,值得将字符串定界符单独放在一行上,这样一切都很容易排列。
- 使用
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;
/
我有一个要求 运行 同时跨多个数据库的 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 层深时,字符串会变得混乱。但是您可以做一些事情让生活更轻松:
- 使用嵌套替代引用机制。比如
q'[ ... ]'
,里面有一个q'< ... >'
,等等 - 使用多行字符串。无需连接多行,只需使用一个字符串即可。
- 使用额外的间距来帮助识别字符串的开头和结尾。当事情变得如此疯狂时,值得将字符串定界符单独放在一行上,这样一切都很容易排列。
- 使用
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;
/