I have a requirement to run a SP across multiple DBs at the same time and one part of it is to eliminate some duplicate records from each DB. Now since the SP can be run multiple times i've included a backup table and and what's needed to truncate and drop it in case the SP is run twice in a row.
Now, since i'm creating tables via DBLINK i've researched that i need to use dbms_utility.exec_ddl_statement - but in this case even though the procedure executes, the truncate and drop queries seem to do nothing, because when i run the SP the second time it fails telling me the name of the backup table is already in use (even though i've included the DROP execution before 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
The rest of the cursor which includes deletes, inserts, updates and creation of GLOBAL TEMP tables seems to work just fine and everything executes. If i manually delete the backup table even the CREATE that fails executes.
I'm perplexed :(
UPDATE 08/12/2016
With the help provided by #Jon Heller I was able to transform my code in the below which works as long as i use a static name for the DB_LINK. But when i try to use the variable it fails.
Tried both of the below versions but stil lcan't get to run however i keep modifying them - am i missing something here?
Note: Now, i added the alter session because without it, re-running the original procedure kept failing due to ORA-04062: timestamp of procedure "cw_drop_table" has been changed;
1st version
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
2nd version
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_
SOLUTION
After much contemplation and a shower i abandoned the above methodology and went with the below. Not sure why i didn't think about it earlier :|
Note: if anyone ever reads through this long-winded question and knows what i did wrong in the 08/12/2016 Update, i am curious to find out :)
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 only reliably runs DDL. If you try to run it with a PL/SQL block it will silently fail and not run anything.
This can be demonstrated by running a PL/SQL block that should obviously fail. The code below should generate ORA-01476: divisor is equal to zero. But instead it does nothing.
begin
dbms_utility.exec_ddl_statement#myself(
q'[declare v_test number; begin v_test := 1/0; end;]'
);
end;
/
Use a temporary procedure to run a PL/SQL block remotely. Create the procedure with DBMS_UTILITY.EXEC_DDL_STATEMENT and then call it with native dynamic 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
I think this behavior is a bug. Oracle should throw an error instead of simply not doing anything.
Welcome to concatenation hell. Strings get messy when they're embedded 4 levels deep. But there are a few things you can do to make life easier:
Use nested alternative-quoting mechanism. For example, q'[ ... ]', inside a q'< ... >', etc.
Use multi-line strings. There's no need to concatenate multiple lines, just use a single string.
Use extra spacing to help identify the start and end of strings. When things get this crazy it's worth putting a string delimiter on a line all by itself, so that everything is easy to line up.
Use REPLACE instead of concatenation.
I re-formatted part of your code using those tips. Stackoverflow does not understand the alternative quoting mechanism, but the strings should look better in a good Oracle SQL editor.
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;
/
Related
oracle execute immediate command concatenation. e.g.
BEGIN EXECUTE IMMEDIATE 'DROP TABLE Foo'; END;
works.
BEGIN EXECUTE IMMEDIATE '''DROP TABLE ' || tableName || ''''; END;
BEGIN EXECUTE IMMEDIATE '''DROP TABLE ' || 'Foo'''; END;
not working.
Table name is an input parameter in stored procedure.
Your single quotes are improper.
Use following:
BEGIN EXECUTE IMMEDIATE 'DROP TABLE ' || tableName; END;
BEGIN EXECUTE IMMEDIATE 'DROP TABLE ' || 'Foo'; END;
Cheers!!
while doing the testing in migrating oracle to PostgreSQL i am facing issue in code:-
create or replace PROCEDURE clear_delete_tables
/*
clear_delete_tables
Truncate all the del_ tables
*/
IS
v_code NUMBER;
v_errm VARCHAR2(64);
CURSOR cur_del_tables IS
SELECT table_name
FROM user_tables
WHERE table_name LIKE 'DEL!_%' ESCAPE '!';
BEGIN
For i in cur_del_tables
LOOP
EXECUTE IMMEDIATE 'TRUNCATE TABLE ' || i.table_name;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
v_code := SQLCODE;
v_errm := SUBSTR(SQLERRM, 1 , 64);
log_entry($$PLSQL_UNIT, v_code || ' - ' || v_errm);
END clear_delete_tables;
What is the replacement in $$PLSQL_UNIT in PostgreSQL? How I can change the entire code to compatible in PostgreSQL?
I need to assign a value to variable from select query output and call the variable into sql commands
For eg: I get PDB_NAME from v$pdbs and assign value to v_pdb
I want to use v_pdb in multiple sql commands to run against PDBs
I tried to assign value from SELECT query to v_pdb and call the v_pdb in 'alter session set container=v_pdb';, it looks like working, but i get ORA-00922: missing or invalid option error
set serveroutput on;
declare
v_sql varchar2(80);
v_pdb varchar2(30);
BEGIN
FOR pdb IN (select name from v$pdbs where con_id=3 and OPEN_MODE='READ WRITE')
LOOP
v_sql := 'alter session set container='||pdb.name;
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
--execute immediate 'alter session set container='||pdb.name||';';
execute immediate v_sql;
--v_sql := 'show con_name';
--execute immediate 'show con_name';
--execute immediate v_sql;
v_sql := 'create tablespace APPDATA datafile '+DATA' size 1G autoextend on next 100M maxsize 5G ENCRYPTION USING 'AES256' DEFAULT STORAGE (ENCRYPT)';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'drop user bigschema cascade';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
--execute immediate 'drop user bigschema cascade';
execute immediate v_sql;
v_sql := 'create user bigschema identified by B67_kuca_ecdf default tablespace APPDATA temporary tablespace TEMP profile DEFAULT account unlock';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'alter user bigschema quota unlimited on APPDATA';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'grant dba to bigschema';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'conn bigschema/"B67_kuca_ecdf"#'||pdb.name;
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'drop table MV2OCI';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'create table MV2OCI tablespace APPDATA as select * from dba_objects';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'alter table MV2OCI nologging';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'show user';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'insert into MV2OCI select * from dba_objects';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
v_sql := 'insert into MV2OCI select * from MV2OCI';
DBMS_OUTPUT.PUT_LINE('Executing: ' || v_sql);
execute immediate v_sql;
END LOOP;
END;
/
I simply want to get the value for variable v_pdb from "select name from v$pdbs where con_id=3 and OPEN_MODE='READ WRITE'"
And call the v_pdb as follows:
alter session set container=v_pdb;
run other sql commands
...
......
I believe the problem is in the trailing semi-colon in your dynamic SQL. Dynamic SQL does not include a trailing semi-colon -- since the dynamic SQL is a single statement, no statement-separator is required.
After dropping the trailing semi-colon (and the "show" command (a client command)) this works ok. But I don't know of a good way to get DBMS_OUTPUT going unless you are already in a given PDB. That has been dropped in this example.
declare
v_sql varchar2(80);
BEGIN
FOR pdb IN (select name from v$pdbs where con_id=3 and OPEN_MODE='READ WRITE')
LOOP
v_sql := 'alter session set container='||pdb.name;
execute immediate V_SQL;
DBMS_OUTPUT.ENABLE;
v_sql := 'CREATE TABLE TEST_TABLE(LOREM_IPSUM NUMBER)';
execute immediate V_SQL;
END LOOP;
END;
/
Result:
PL/SQL procedure successfully completed.
Navigating over to the PDB, TEST_TABLE now exists there.
I do not think that it actually has anything to do with your pdb variable...
When you use execute immediate you can not have a ; in the string
So for each of your execute immediate statments remove the ; eg
execute immediate 'alter session set container='||pdb.name||';';
becomes
execute immediate 'alter session set container='||pdb.name;
There are several ways to improve the code and the coding process:
Exclude statement terminators from dynamic SQL: As others have mentioned, remove the ; from the end of SQL statements used in dynamic SQL.
Escape strings: Strings in strings need to be escaped. The string 'DATA' should be ''DATA''.
Pay attention to the full error message: Always display the entire error message, including the line number and column number. That information points exactly to the problem.
Use the smallest possible example: A smaller example would have less errors, making it easier to find the real problem. And in the process of simplifying the example you will likely find the answer yourself.
I get tripped up using dynamic sql and quotes. When I dbms_output the sql_stmt, it outputs valid/working code. How should the sql_stmt := line be written so that I can dynamically execute it? I have tried ":1 using" bind variable syntax as well with no luck.
The point of this code is to gather stats for a subset of tables.
set serveroutput on
--create this test table for working example.
--create table test3 as select table_name from user_tables where rownum <= 5;
declare
sql_stmt varchar2(500);
begin
for rec in (select table_name from test3)
loop
sql_stmt := 'exec dbms_stats.gather_table_stats (''SCOTT'',''' || rec.table_name || ''')';
dbms_output.put_line(sql_stmt);
execute immediate sql_stmt; -- <---Error is here---
end loop;
end;
The errors I get from the execute immediate sql_stmt; line is:
ORA-00900: invalid SQL statement
ORA-06512: at line 8
EXEC is an SQL*Plus command. You may want to wrap the call to DBMS_STATS.GATHER_TABLE_STATS in an anonymous block instead if you insist on using dynamic SQL.
However, you should be able to call the procedure directly, like so:
declare
sql_stmt varchar2(500);
begin
for rec in (select table_name from test3)
loop
dbms_stats.gather_table_stats ('SCOTT',rec.table_name);
end loop;
end;
Use
sql_stmt := 'BEGIN dbms_stats.gather_table_stats (''SCOTT'','''
|| rec.table_name || '''); END;';
instead.
I have a database named Tamaris, which contains a table User. I created a trigger to create a new user in database each time a row is inserted in my User table. Here's the PL/SQL code :
CREATE OR REPLACE
TRIGGER UTILISATEUR_CREATE_USER_TRG
AFTER INSERT ON UTILISATEUR
FOR EACH ROW
DECLARE
nom_compte NVARCHAR2(20 CHAR);
str_create VARCHAR2(300);
str_grant VARCHAR(250);
type_compte NUMBER;
unauthorized_exception EXCEPTION;
BEGIN
CASE
WHEN :new.idtypecompte = 1 THEN
nom_compte := :new.pseudoutilisateur;
type_compte := 1;
WHEN :new.idtypecompte = 2 THEN
nom_compte := 'AC_'|| :new.pseudoutilisateur;
type_compte := 2;
WHEN :new.idtypecompte = 3 THEN
RAISE unauthorized_exception;
END CASE;
str_create := 'CREATE USER '|| nom_compte ||' IDENTIFIED BY '|| :new.passwordutilisateur ||' DEFAULT TABLESPACE tamaris TEMPORARY TABLESPACE temp QUOTA UNLIMITED ON tamaris;' ;
EXECUTE IMMEDIATE str_create;
IF type_compte = 1 THEN
str_grant := 'GRANT Base_User TO '|| nom_compte ||';' ;
EXECUTE IMMEDIATE str_grant;
ELSE
str_grant := 'GRANT Adv_User TO '|| nom_compte ||';' ;
EXECUTE IMMEDIATE str_grant;
END IF;
EXCEPTION
WHEN unauthorized_exception THEN
dbms_output.put_line('Impossible de créer un autre gestionnaire');
END;
When I insert a row in table User, the trigger fires and I get this :
Error during saving of modifications on "TAMARIS"."UTILISATEUR" :
Line 3 : ORA-00911: Invalid character
ORA-06512: at "TAMARIS.UTILISATEUR_CREATE_USER_TRG", Line 22
ORA-04088: Error in the execution of 'TAMARIS.UTILISATEUR_CREATE_USER_TRG'
ORA-06512: at Line 1
For the record, the request in str_create is working outside the trigger with random parameter (only if wrapped with BEGIN; END;). Therefore I tried :
str_create := 'BEGIN CREATE USER '|| nom_compte ||' IDENTIFIED BY '|| :new.passwordutilisateur ||' DEFAULT TABLESPACE tamaris TEMPORARY TABLESPACE temp QUOTA UNLIMITED ON tamaris; END;' ;
Still not working. I would appreciate any inputs on this, thanks.
EDIT :
Content of my procedure as suggested:
CREATE OR REPLACE
PROCEDURE CREATE_USER_IN_DB(p_username IN NVARCHAR2, p_password IN UTILISATEUR.passwordutilisateur%type, p_type IN NUMBER ) AS
BEGIN
EXECUTE IMMEDIATE 'CREATE USER '|| p_username ||' IDENTIFIED BY '|| p_password ||' DEFAULT TABLESPACE tamaris
TEMPORARY TABLESPACE temp QUOTA UNLIMITED ON tamaris';
IF p_type = 1 THEN
EXECUTE IMMEDIATE 'GRANT Base_User TO '|| p_username;
ELSE
EXECUTE IMMEDIATE 'GRANT Adv_User TO '|| p_username;
END IF;
END CREATE_USER_IN_DB;
EDIT2 :
How I call the procedure outside of trigger :
BEGIN
CREATE_USER_IN_DB('whatever','quickpass', 2);
END;
I get
ORA-00900:
Invalid SQL instruction
ORA-06512: at "TAMARIS.CREATE_USER_IN_DB", line 3
ORA-06512: at line 2
00900. 00000 - "invalid SQL statement"
*Cause:
*Action:
1) As #Bob Jarvis suggested, when you build a SQL statement that you intend to pass to EXECUTE IMMEDIATE, that SQL statement should not contain a trailing semicolon ;
2) Since CREATE USER and GRANT are DDL statements, they issue implicit commits before and after they are executed. That means that you cannot call DDL statements in a trigger since a trigger cannot cause a transaction to be ended. If you really want to do this (and creating users as a side effect of inserting a row in a table seems like a very problematic architecture), you would have to do it asynchronously. Your trigger can call DBMS_JOB to schedule a job to run after your current transaction completes and that job can execute DDL statements. For example, if you create a procedure that actually creates the user (this is where all your DDL would go)
CREATE PROCEDURE create_user( p_username IN NVARCHAR2,
p_password IN UTILISATEUR.passwordutilisateur%type,
p_type IN NUMBER )
AS
<<implement procedure>>
then your trigger could do something like
CREATE OR REPLACE
TRIGGER UTILISATEUR_CREATE_USER_TRG
AFTER INSERT ON UTILISATEUR
FOR EACH ROW
DECLARE
nom_compte NVARCHAR2(20 CHAR);
str_create VARCHAR2(300);
str_grant VARCHAR(250);
type_compte NUMBER;
l_jobno PLS_INTEGER;
unauthorized_exception EXCEPTION;
BEGIN
CASE
WHEN :new.idtypecompte = 1 THEN
nom_compte := :new.pseudoutilisateur;
type_compte := 1;
WHEN :new.idtypecompte = 2 THEN
nom_compte := 'AC_'|| :new.pseudoutilisateur;
type_compte := 2;
WHEN :new.idtypecompte = 3 THEN
RAISE unauthorized_exception;
END CASE;
dbms_job.submit( l_jobno,
'BEGIN create_user( ''' || nom_compte || ''', ' ||
'''' || :new.passwordutilisateur || ''', ' ||
type_compte || '); END;' );
END;