PL-SQL - How to add a exception for this loop? - oracle

I have a script that I need to use to deploy some changes in the database. For it I need to kill all sessions before to start the changes.
set serveroutput on;
begin
for rec in (SELECT se.sid,se.serial#,se.inst_id FROM gv$session se where type <> 'BACKGROUND' and username not in ('SYSRAC','SYS') and sid <> (select sys_context('userenv','sid') from dual) and status <> 'KILLED')
loop
execute immediate 'Alter System Kill Session '''|| rec.Sid|| ',' || rec.Serial# || ',#' ||rec.inst_id||''' IMMEDIATE';
dbms_output.put_line('Eliminada: '||rec.sid);
end loop;
end;
/
The problem is: I have a database with more than 3000 connections. While this script is executing it is normal for some sessions to disconnect by themselves and it result in the following error:
ERROR at line 1:
ORA-00030: User session ID does not exist.
ORA-06512: at line 4
ORA-06512: at line 4
How can I control the loop to ignore the sessions that disconnect from the database for themselves?

I would do this:
set serveroutput on;
declare
sess_not_exist exception;
pragma exception_init(sess_not_exist, -30);
begin
for rec in (SELECT se.sid,se.serial#,se.inst_id FROM gv$session se where type <> 'BACKGROUND' and username not in ('SYSRAC','SYS')
and sid <> (select sys_context('userenv','sid') from dual) and status <> 'KILLED')
loop
begin
execute immediate 'Alter System Kill Session '''|| rec.Sid|| ',' || rec.Serial# || ',#' ||rec.inst_id||''' IMMEDIATE';
dbms_output.put_line('Eliminada: '||rec.sid);
exception
when sess_not_exist then null;
when others then raise;
end;
end loop;
end;
/
The reasons:
if the session no longer exists between the cursor and the execution, you should not raise an error
you must encapsulate the unit which runs the execute immediate to treat the exception in any entry in the loop, hence the double begin end section.
I think you want to raise any other exception, therefore I put when others then raise.

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.

Why am I getting ORA-00900 Invalid SQL statement when trying to create a directory object with execute immediate?

I see many examples of this being possible in Oracle. It's just not working for me. Oracle 11. I am getting this error on line 15. Thank you all!
declare
v_path nvarchar2(256);
v_object_exists number;
begin
-- Use the directory Oracle DB provide for tracing.
select VALUE into v_path from V$DIAG_INFO where NAME = 'Diag Trace';
--dbms_output.put_line(v_path);
-- Set up new directory!
select count(*) into v_object_exists from all_objects where object_name = 'DIAG_TRACE' and object_type = 'DIRECTORY';
if v_object_exists > 0 then
execute immediate 'DROP DIRECTORY DIAG_TRACE';
end if;
dbms_output.put_line('CREATE OR REPLACE DIRECTORY DIAG_TRACE AS ''' || v_path || '''');
execute immediate 'CREATE OR REPLACE DIRECTORY DIAG_TRACE AS ''' || v_path || '''';
end;
This appears to be a bug; though not one listed on the ORA-00900 reference note on MoS.
It doesn't like the path variable being concatenated in as part of the execute immediate. This fails:
v_path := '/some/path';
execute immediate 'CREATE OR REPLACE DIRECTORY DIAG_TRACE AS ''' || v_path || '''';
but this is OK, even though the final statement is the same:
execute immediate 'CREATE OR REPLACE DIRECTORY DIAG_TRACE AS ''/some/path''';
After some searching, it may be something to do with bug 7036176: "CONCATENATED DYNAMIC OBJECT NAME RAISES ORA-00900 IN 10G & 11G". It's not exactly the same but close. You'll need to look on My Oracle Support for further info, though there isn't much.
You can work around it with a variable:
declare
v_stmt varchar2(256);
v_path nvarchar2(256);
v_object_exists number;
begin
-- Use the directory Oracle DB provide for tracing.
select VALUE into v_path from V$DIAG_INFO where NAME = 'Diag Trace';
--dbms_output.put_line(v_path);
-- Set up new directory!
select count(*) into v_object_exists from all_objects where object_name = 'DIAG_TRACE' and object_type = 'DIRECTORY';
if v_object_exists > 0 then
execute immediate 'DROP DIRECTORY DIAG_TRACE';
end if;
v_stmt := 'CREATE OR REPLACE DIRECTORY DIAG_TRACE AS ''' || v_path || '''';
dbms_output.put_line(v_stmt);
execute immediate v_stmt;
end;
/
Which saves repeating the string to print it, though you might only have done that because of this issue.
Not sure why you're dropping first with or replace, incidentally.
I prefer putting a command into a variable, display it (for verification) and then execute it:
SQL> declare
2 v_path nvarchar2(256);
3 v_object_exists number;
4 l_str varchar2(200);
5 begin
6 -- Use the directory Oracle DB provide for tracing.
7 select VALUE into v_path from V$DIAG_INFO where NAME = 'Diag Trace';
8 --dbms_output.put_line(v_path);
9
10 -- Set up new directory!
11 select count(*) into v_object_exists from all_objects where object_name = 'DIAG_TRACE' and object_type = 'DIRECTORY';
12 if v_object_exists > 0 then
13 execute immediate 'DROP DIRECTORY DIAG_TRACE';
14 end if;
15 l_str := 'CREATE OR REPLACE DIRECTORY DIAG_TRACE AS ' || chr(39) || v_path ||chr(39);
16 dbms_output.put_line(l_str);
17 execute immediate l_str;
18 end;
19 /
CREATE OR REPLACE DIRECTORY DIAG_TRACE AS
'C:\ORACLEXE\APP\ORACLE\diag\rdbms\xe\xe\trace'
PL/SQL procedure successfully completed.
SQL>
Certainly, you have to run it as a privileged user (such as SYS).

Detecting a Deadlock in a Stored Procedure

I am trying to write a deadlock proof stored procedure. It is based on the following concept.
I have been trying to write a stored procedure which is based on the following concept. The procedure will try to drop a constraint on a table and if in case it detects a deadlock situation, it wait for some time before trying again. The important thing is it should only retry in case of a Deadlock or a NOWAIT error, all other errors are to be handled via exceptions.
Procedure test
is
BEGIN
<<label>>
DROP constraint on a table
if (deadlock(ORA-00060)/Nowait Error (ORA-0054)) detected
then
sleep for 60 seconds
Goto label
exception
when others.
It would be great if any of the experts please help me with this. A similar example would be highly helpful. Thank you for your help.
While some people harbour an irrational aversion to goto it remains true that usually we can implement the same logic without using the construct. That is true here: a simple while loop is all that is necessary:
create or replace procedure drop_constraint_for_sure
( p_table_name in varchar2
, p_constraint_name in varchar2
)
is
x_deadlock exception;
pragma exception_init(x_deadlock, -60);
x_nowait exception;
pragma exception_init(x_nowait, -54);
begin
loop
begin
execute immediate 'alter table '|| p_table_name
|| ' drop constraint ' || p_constraint_name
|| ' cascade' ;
exit;
exception
when x_deadlock then null;
when x_nowait then null;
end;
dbms_lock.sleep(60);
end loop;
end;
/
Note that the sleep function requires the execute privilege on SYS.DBMS_LOCK. This is not granted by default, so if you don't have it you'll need to ask your friendly DBA to grant it.
Also note that this implementation doesn't have any form of abort. So it will loop eternally, until the constraint is dropped or some other exception occurs. In real life you should include a loop count with an additional exit test on a threshold for the count. Although in real life I probably wouldn't want a stored procedure like this anyway: I prefer knowing as soon as possible when someone is using a table I'm trying to alter.
Hope below snippet gives you can an idea to achieve your requirement.
CREATE OR REPLACE
PROCEDURE DRP_CONST_DEALDOCK
AS
DEADLOCK_EX EXCEPTION;
NO_WAIT_EX EXCEPTION;
PRAGMA EXCEPTION_INIT(DEADLOCK_EX,-60);
PRAGMA EXCEPTION_INIT(NO_WAIT_EX, -54);
lv_cnt PLS_INTEGER;
BEGIN
BEGIN
EXECUTE IMMEDIATE 'ALTER TABLE STACK_OF_TEST DROP CONSTRAINT SYS_C00375020';
EXCEPTION
WHEN DEADLOCK_EX THEN
dbms_output.put_line('nowait exception');
DBMS_LOCK.SLEEP(10);
SELECT COUNT(1)
INTO lv_cnt
FROM v$locked_object
WHERE object_id IN
(SELECT OBJECT_ID FROM DBA_OBJECTS WHERE OBJECT_NAME = 'STACK_OF_TEST'
);
WHILE lv_cnt > 0
LOOP
dbms_lock.sleep(10);
SELECT COUNT(1)
INTO lv_cnt
FROM v$locked_object
WHERE object_id IN
(SELECT OBJECT_ID FROM DBA_OBJECTS WHERE OBJECT_NAME = 'STACK_OF_TEST'
);
END LOOP;
WHEN NO_WAIT_EX THEN
dbms_output.put_line('nowait exception');
DBMS_LOCK.SLEEP(10);
SELECT COUNT(1)
INTO lv_cnt
FROM v$locked_object
WHERE object_id IN
(SELECT OBJECT_ID FROM DBA_OBJECTS WHERE OBJECT_NAME = 'STACK_OF_TEST'
);
WHILE lv_cnt > 0
LOOP
dbms_lock.sleep(10);
SELECT COUNT(1)
INTO lv_cnt
FROM v$locked_object
WHERE object_id IN
(SELECT OBJECT_ID FROM DBA_OBJECTS WHERE OBJECT_NAME = 'STACK_OF_TEST'
);
END LOOP;
WHEN OTHERS THEN
dbms_output.put_line(SQLCODE||'-->'||SQLERRM);
END;
END;

Exception dose not working in execute immediate insert in oracle

I have some problem in execute immediate insert statement exception part.
I have a table query_tb that contains two columns (DEPT and SOURCE_VALUE)
The column contains data in below
CLERK
select a.empno,a.ename,a.job,a.mgr,a.hiredate,b.deptno,b.dname,b.loc
from emp a,dept b where a.deptno=b.deptno and a.empno= '#V_GCIF#'
SALESMAN
select e.empno,e.ename,e.job,d.deptno,d.dname,d.loc from emp e,dept
d where e.deptno=d.deptno and e.empno= '#V_GCIF#'
MANAGER
select a.empno,a.ename,a.job,b.deptno,b.dname,b.loc from employee
a,department b where a.deptno=b.deptno and a.empno= '#V_GCIF#'
ADMIN
select a.empno,a.ename,a.job,b.deptno,b.dname,b.loc from employee
a,department b where a.deptno=b.deptno and a.empno= '#V_GCIF#'
If I pass the correct empno which is keep on the emp table it runs fine. But if I pass the incorrect empno (no data) then exception part not working.
create or replace
PROCEDURE test_emp_sp(
p_id IN VARCHAR2)
AS
CURSOR rec
IS
SELECT dept,
source_value
FROM query_tb;
v_query VARCHAR2(1000);
BEGIN
FOR rec IN
(SELECT dept,source_value FROM query_tb
)
LOOP
IF rec.dept='CLERK' THEN
v_query :=REPLACE(rec.source_value,'#V_GCIF#',p_id);
EXECUTE IMMEDIATE 'INSERT INTO emp_tb (empno,ename,job,mgr,hiredate,deptno,dname,loc) ('||v_query|| ')';
dbms_output.put_line(v_query||' inserted');
ELSE
v_query:=REPLACE(rec.source_value,'#V_GCIF#',p_id);
EXECUTE IMMEDIATE 'INSERT INTO emp_tb (empno,ename,job,deptno,dname,loc) ('||v_query||')';
dbms_output.put_line(v_query||' inserting others');
END IF;
END LOOP;
commit;
EXCEPTION
WHEN others THEN
dbms_output.put_line('No data Found...');
END;
That's because you are not running a select command, it is an insert command (insert select) which means that if the select won't return rows it just doesn't insert anything and no error is thrown for that. You should check whether the insert command has affected any rows. The way you do that in Oracle is checking the SQL%ROWCOUNT immediate after the execution, if it turns to be 0 then you do your job raising an exception. It would be something like:
DECLARE
customException EXCEPTION;
PRAGMA EXCEPTION_INIT( customException, -20001 );
....
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO emp_tb (empno,ename,job,mgr,hiredate,deptno,dname,loc)
('||v_query|| ')';
IF (SQL%ROWCOUNT) = 0 then
raise_application_error( -20001, 'This is a custom error' );
end if;
EXCEPTION
WHEN customException THEN
dbms_output.put_line('No data Found...');
END;
It's a long time without programming in Oracle PLSql So somethings there on the provided code may not compile but it is all there look into it in the internet and you will be good.
Exception does not work because there is no exception.
If SELECT returns NULL then 0 rows will be INSERT.
Example:
insert into t1(c1)
select 100 from dual where 1=0;
Result: 0 rows inserted.
insert into ... select from..
will not raise a no_data_found exception. It will just insert 0 records.
To test if you have inserted any records you can use SQL%ROWCOUNT after the insert statement.
execute immedate 'INSERT...;
if SQL%ROWCOUNT=0
then
dbms_output.put_line('no records inserted');
else
...
end if;
Also you might want to consider changing #V_GCIF# into something that can be used as a bind varaiable such as :P_ID.
You can skip the replace statement and change the execute immediate into something like:
execute immediate 'INSERT INTO ...'||v_query
using p_id;
This will bind the value of p_id to the :p_id in the statement.
The very basic thing here is to capture the error appropriately so that you can able to track back. Here i have put loggers and EXCEPTION handlers jsut to check the error. Also i have put a RAISE_APPLICATION_ERROR. Did some modifications too in the snipet. Hope it helps.
PS: Have not checked for the syntax as i dont have workspace now with me.
CREATE OR REPLACE
PROCEDURE test_emp_sp(
p_id IN VARCHAR2)
AS
--Not needed
-- CURSOR rec
-- IS
-- SELECT dept,
-- source_value
-- FROM query_tb;
--Not needed
v_query VARCHAR2(1000);
BEGIN
FOR rec IN
(SELECT dept,source_value FROM query_tb
)
LOOP
IF rec.dept='CLERK' THEN
v_query :=REPLACE(rec.source_value,'#V_GCIF#',p_id);
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO emp_tb (empno,ename,job,mgr,hiredate,deptno,dname,loc) ('||v_query|| ')';
dbms_output.put_line(v_query||' inserted');
EXCEPTION WHEN OTHERS THEN
dbms_output.put_line(' Error while inserting data in emp_tab for Clerk--> '||SQLCODE||'---'||SQLERRM);
END;
ELSE
v_query:=REPLACE(rec.source_value,'#V_GCIF#',p_id);
BEGIN
EXECUTE IMMEDIATE 'INSERT INTO emp_tb (empno,ename,job,deptno,dname,loc) ('||v_query||')';
dbms_output.put_line(v_query||' inserting others');
EXCEPTION WHEN OTHERS THEN
dbms_output.put_line(' Error while inserting data in emp_tab for other then clerk --> '||SQLCODE||'---'||SQLERRM);
END;
END IF;
END LOOP;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
RAISE_APPLICATION_ERROR(-20001,'Error occurred in the plsql block',TRUE);
END;
/

Dynamic SQL and ORA-00911

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;

Resources