Assign value to Variable and call in multiple commands - oracle

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.

Related

SQL Command Not Properly Ended for EXECUTE IMMEDIATE

I am trying to write a script that will end sessions. I get the SQL command not properly ended when trying to EXECUTE IMMEDIATE and I can't figure out what I am doing wrong and why I am getting the error message. I have removed the ; form the line cur_show_connection and that didn't work as well.
|| ''' IMMEDIATE' sqlstatement
Here is my full code
declare
cursor cur_show_connections is
SELECT 'ALTER SYSTEM DISCONNECT SESSION '''
|| SID
|| ','
|| SERIAL#
|| ''' IMMEDIATE;' sqlstatement
FROM v$session
WHERE machine = 'XXXXX';
sqlstr varchar2(3200) :='';
begin
for rec_show_connections in cur_show_connections loop
sqlstr := sqlstr || rec_show_connections.sqlstatement || chr(13) || chr(10);
--Tried here and this didnt work either
--EXECUTE IMMEDIATE sqlstr;
end loop;
dbms_output.put_line(sqlstr);
EXECUTE IMMEDIATE sqlstr;
--This seems to work hardcoding the values
--EXECUTE IMMEDIATE q'[ALTER SYSTEM DISCONNECT SESSION '425,7516' IMMEDIATE]';
end;
Any help would be appreciated.
Don't end your ALTER SYSTEM command with a semi-colon. The EXECUTE IMMEDIATE command will take care of that.
cursor cur_show_connections is
SELECT 'ALTER SYSTEM DISCONNECT SESSION '''
|| SID
|| ','
|| SERIAL#
|| ''' IMMEDIATE' sqlstatement
FROM v$session
WHERE machine = 'XXXXX';
You're also concatenating your commands into a single EXECUTE IMMEDIATE statement. That won't work the way you're doing it: EXECUTE IMMEDIATE can only run one command or PL/SQL block at a time.
for rec_show_connections in cur_show_connections loop
dbms_output.put_line(rec_show_connections.sqlstatement);
EXECUTE IMMEDIATE rec_show_connections.sqlstatement;
end loop;
OR
sqlstr = 'BEGIN ';
for rec_show_connections in cur_show_connections loop
sqlstr := sqlstr || dbms_output.put_line(rec_show_connections.sqlstatement) || '; ';
end loop;
sqlstr := sqlstr || ' END;'
EXECUTE IMMEDIATE sqlstr;
Depending on how many sessions you might need to disconnect, the first option is less likely to have issues with character string size limitations. PL/SQL has a 32K limitation that could affect how big a single PL/SQL concatenated block could get, potentially throwing an error if you tried to kill too many sessions at once.

oracle execute immediate command concatenation

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!!

Oracle - cursor using dbms_utility.exec_ddl_statement not executing properly

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;
/

anonymous block in 11g

I need a help on this.
I am trying to create a pl/sql anonymous block and when I run that it shows its completed but it don't run the code. It should give me an error saying name is already used by an existing object. Can someone help me on this.
I am actually creating procedures but just trying this code as a sample.
DECLARE
V_REF_TBL VARCHAR2(100);
V_SQL LONG;
begin
V_REF_TBL :='My_TABLE';
v_SQL :='truncate table '||V_REF_TBL ;
EXECUTE IMMEDIATE V_SQL;
EXECUTE IMMEDIATE 'CREATE TABLE '|| V_REF_TBL ||' parallel 9 nologging pctfree 0 as
select * from dual';
End;
Possibly you're looking for this:
<<my_block>>
Declare
table_name varchar2(30);
counter number;
Begin
table_name := 'my_table';
select count(*)
into counter
from user_tables
where table_name = Upper(Trim(my_block.table_name)) and
dropped = 'NO';
if counter = 0
then
execute immediate 'create table '||table_name||' as ... etc';
else
execute immediate 'truncate table '||table_name;
execute immediate 'insert into '||table_name' select ... etc';
end if;
end my_block;

How can I alter a sequence in dynamic SQL?

I'm trying to create a script to migrate data from one DB to another. One thing I'm not currently able to do is set the nextval of a sequence to the nextval of a sequence in another DB.
I got the difference in values from user_sequences and generated the following dynamic SQL statements:
execute immediate 'alter sequence myseq increment by 100';
execute immediate 'select myseq.nextval from dual';
execute immediate 'alter sequence myseq increment by 1';
commit;
But nothing happens. What am I missing? If I run the same statements outside the procedure, they work fine:
alter sequence myseq increment by 100;
select myseq.nextval from dual;
alter sequence myseq increment by 1;
commit;
EDIT: Apologies to all for not being clear. I'm actually altering the sequence in the same DB. I'm only getting the value to be set from a remote DB. Perhaps it was unnecessary to mention the remote DB as it doesn't affect things. I only mentioned it to explain what my goals were.
Step 1. I get the nextval of the sequence from a remote DB.
select (select last_number
from dba_sequences#remoteDB
where upper(sequence_name) = upper(v_sequence_name)) - (select last_number
from user_sequences
where upper(sequence_name) = upper(v_sequence_name)) increment_by
from dual;
Step 2. I generate dynamic SQL statements with this value:
execute immediate 'alter sequence myseq increment by 100';
execute immediate 'select myseq.nextval from dual';
execute immediate 'alter sequence myseq increment by 1';
commit;
No error was raised, but nothing happened. When I wrote the SQL statements with DBMS_OUTPUT.PUT_LINE and ran them outside they worked.
Here is some code which dynamically sets a sequence to a new (higher) value. I have written this so it will work for any sequence in your schema.
create or replace procedure resync_seq
(p_seq_name in user_sequences.sequence_name%type)
is
local_val pls_integer;
remote_val pls_integer;
diff pls_integer;
begin
execute immediate 'select '|| p_seq_name ||'.nextval from dual'
into local_val;
select last_number into remote_val
from user_sequences#remote_db
where sequence_name = p_seq_name ;
diff := remote_val - local_val;
if diff > 0
then
execute immediate 'alter sequence '|| p_seq_name ||' increment by ' ||to_char(diff);
execute immediate 'select '|| p_seq_name ||'.nextval from dual'
into local_val;
execute immediate 'alter sequence '|| p_seq_name ||' increment by 1';
end if;
end;
The procedure doesn't need a COMMIT because DDL statements issue an implicit commit (two in fact).
You can execute it and see the synced value like this (in SQL*PLus):
exec resync_seq('MYSEQ')
select myseq.currval
from dual
Incidentally, the only way to reset a sequence (to its original starting value or a different lower value) is dropping and re-creating the sequence.
In 18c Oracle added a RESTART capability to ALTER SEQUENCE. The straightforward option ...
alter sequence myseq restart;
...resets the sequence to the value specified by the START WITH clause in the original CREATE SEQUENCE statement. The other option allows us to specify a new starting point:
alter sequence myseq restart start with 23000;
Excitingly this new starting point can be ahead or behind the current value (within the usual bounds of a sequence).
The one snag is that this new capability is undocumented (only for Oracle's internal usage) and so we're not supposed use it. Still true in 20c. The only approved mechanism for changing a sequence's value is what I outlined above.
I wasn't quite able to understand what you mean, but here is a wild guess:
I don't see it in your code, but you're talking about executing DDL (CREATE, ALTER etc.) on another database, so I assume you are using Database Links. It is not possible to use Database Links to execute DDL on another database. You might want to consider that.
After the information you provided, this might be what you need. And if you want to set the current value of the sequence, you can't, according to this documentation, you need to drop/create:
declare
ln_lastNumber DBA_SEQUENCES.LAST_NUMBER%type;
lv_sequenceName DBA_SEQUENCES.SEQUENCE_NAME%type := 'MYSEQ';
begin
select LAST_NUMBER
into ln_lastNumber
from DBA_SEQUENCES--or #remote_db;
where
--Your predicates;
execute immediate 'drop sequence ' || lv_sequenceName;
execute immediate 'create sequence ' || lv_sequenceName || ' starts with ' || ln_lastNumber;
exception
when no_data_found then
dbms_output.put_line('No sequence found!'); -- Or log somehow.
raise;
when others then
raise;
end;
Also, DDL in dynamic SQL pacakges requires
AUTHID CURRENT_USER
when declaring the package specification, if you want it to have the credentials of the current user
I took the code provided by APC and modified as below:
create or replace procedure resync_seq
(p_seq_name in user_sequences.sequence_name%type,
p_table_name in user_tables.table_name%type,
p_pk in user_cons_columns.column_name%type)
is
local_val pls_integer;
remote_val pls_integer;
diff pls_integer;
begin
execute immediate 'select '|| p_seq_name ||'.nextval from dual'
into local_val;
execute immediate 'select max('||p_pk||') from '||p_table_name ||' '
into remote_val ;
diff := remote_val - local_val;
if diff > 0
then
execute immediate 'alter sequence '|| p_seq_name ||' increment by ' ||to_char(diff);
execute immediate 'select '|| p_seq_name ||'.nextval from dual'
into local_val;
execute immediate 'alter sequence '|| p_seq_name ||' increment by 1';
end if;
end;

Resources