Oracle: Invalid ALTER command in execute immediate - oracle

In the procedure, in the ALTER command, I need to dynamically substitute the name of the trigger that needs to be activated.
declare
v_trg_name varchar2(25) := 'article_comment_audit';
begin
execute immediate 'ALTER TRIGGER' || v_trg_name || 'ENABLE';
end;
I try to run this code, but it returns an error ORA-00940 invalid ALTER command
Please tell me what is the problem?

You'll get 'ALTER TRIGGERarticle_comment_auditENABLE'.
Insert Blanks:
'ALTER TRIGGER ' || v_trg_name || ' ENABLE';
in order to get 'ALTER TRIGGER article_comment_audit ENABLE'.

Related

Assign value to Variable and call in multiple commands

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.

PL/SQL Triggers with dynamic sql

I want to create ddl trigger(on create) which will create a dml trigger
But i have error:
ORA-06512: на line 8
00604. 00000 - "error occurred at recursive SQL level %s"
*Cause: An error occurred while processing a recursive SQL statement
(a statement applying to internal dictionary tables).
*Action: If the situation described in the next error on the stack
can be corrected, do so; otherwise contact Oracle Support.
CREATE OR REPLACE TRIGGER test_ddl
after CREATE ON SCHEMA
DECLARE
user_col VARCHAR(5) := 'user_';
time_col VARCHAR(5) := 'time_';
BEGIN
IF ora_dict_obj_type = 'TABLE'
THEN
EXECUTE IMMEDIATE 'alter table ' || ora_dict_obj_name || ' add(' || user_col || ' varchar(20), '|| time_col ||' timestamp)'||'';
EXECUTE IMMEDIATE 'CREATE OR REPLACE TRIGGER add_user_time BEFORE INSERT OR UPDATE ON test_tab FOR EACH ROW BEGIN ' || ':' || 'new.time_ := sysdate; END';
END IF;
END;
/
DROP TABLE test_tab PURGE;
/
CREATE TABLE test_tab(ID NUMBER);
At the very least, I expect you want new.time_ to be :new.time_
You are missing a semicolon after the END.
Replace this
EXECUTE IMMEDIATE 'CREATE OR REPLACE TRIGGER add_user_time BEFORE INSERT OR UPDATE ON test_tab FOR EACH ROW BEGIN ' || ':' || 'new.time_ := sysdate; END';
with that
EXECUTE IMMEDIATE 'CREATE OR REPLACE TRIGGER add_user_time BEFORE INSERT OR UPDATE ON test_tab FOR EACH ROW BEGIN ' || ':' || 'new.time_ := sysdate; END;';
Full error messages are:
ORA-00604: error occurred at recursive SQL level 1
ORA-24344: success with compilation error
ORA-06512: at line 8
Important part was:
ORA-24344: success with compilation error
The source code should read:
CREATE OR REPLACE TRIGGER test_ddl
after CREATE ON SCHEMA
DECLARE
user_col VARCHAR(5) := 'user_';
time_col VARCHAR(5) := 'time_';
BEGIN
IF ora_dict_obj_type = 'TABLE'
THEN
EXECUTE IMMEDIATE 'alter table ' || ora_dict_obj_name || ' add(' || user_col || ' varchar(20), '|| time_col ||' timestamp)'||'';
EXECUTE IMMEDIATE 'CREATE OR REPLACE TRIGGER add_user_time BEFORE INSERT OR UPDATE ON test_tab FOR EACH ROW BEGIN ' || ':' || 'new.time_ := sysdate; END;';
END IF;
END;
/
DROP TABLE test_tab PURGE;
/
CREATE TABLE test_tab(ID NUMBER);
Just as an explanation: I have put some log messages to see where it exactly fails. Then you can see that the trigger is recursively called upon the CREATE OR REPLACE TRIGGER but with an ora_dict_obj_type = TRIGGER

Create Oracle users in a loop with default tablespace

I try to execute this but it gives an error at the default tablespace. I want to give a default tablespace to the users I create but how?
BEGIN
FOR USERNAME IN (SELECT studentname from students)
LOOP
EXECUTE IMMEDIATE 'CREATE USER ' || USERNAME.studentname || ' IDENTIFIED BY ' || USERNAME.studentname;
EXECUTE IMMEDIATE 'DEFAULT TABLESPACE "USERS"';
EXECUTE IMMEDIATE 'TEMPORARY TABLESPACE "TEMP"';
EXECUTE IMMEDIATE 'GRANT STUDENT TO ' || USERNAME.studentname ;
END LOOP;
END;
Error report - ORA-00900: invalid SQL statement ORA-06512: at line 5 00900. 00000 - "invalid SQL
You need to combine the first three statements into one and add in appropriate spaces. You don't need the double-quotes around the tablespace names since you're using case-insensitive identifiers.
BEGIN
FOR USERNAME IN (SELECT studentname from students)
LOOP
EXECUTE IMMEDIATE 'CREATE USER ' || USERNAME.studentname || ' IDENTIFIED BY ' || USERNAME.studentname ||
' DEFAULT TABLESPACE USERS
TEMPORARY TABLESPACE TEMP';
EXECUTE IMMEDIATE 'GRANT UNLIMITED TABLESPACE TO ' || USERNAME.studentname;
EXECUTE IMMEDIATE 'GRANT STUDENT TO ' || USERNAME.studentname ;
END LOOP;
END;
Personally, I'd always generate a string with the SQL statement and pass that string to EXECUTE IMMEDIATE. That makes it easy to do things like log the SQL statements that are executed or to log which statement failed. That's going to make debugging far easier.

ORA - 04020: deadlock detected while trying to lock object

Getting an ORA-04020: deadlock detected while trying to lock object and I believe the source of the error could be these statements:
v_sql := 'DELETE FROM ' || in_table_name || ' SUBPARTITION (' || v_subpart_name || ')';
EXECUTE IMMEDIATE v_sql;
v_sql := 'ALTER TABLE ' || in_table_name || ' TRUNCATE SUBPARTITION ' || v_subpart_name;
EXECUTE IMMEDIATE v_sql;
Any ideas on how to resolve this issue? Could it be the ALTER statement is throwing the error since the DELETE is right before it? Not sure, I thought the ALTER would execute only once the DELETE if finished. Or could it be the procedure doesn't wait for the ALTER to complete before exiting and re-executing?
if your already doing TRUNCATE SUBPARTITION then why you need to delete . TRUNCATE would be much efficient way to delete data from table.
other wise you have to do commit after delete , then only truncate is allowed.
Regards
Ramki

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