I am trying to use EXCEPTION WHEN OTHERS to catch non-existent tables that I try to DROP, as follows:
begin
execute immediate 'drop table X';
exception when others then null;
end;
If table x exists and I run this, the table is dropped and all is well. If I run it again, there is no table to be dropped, but the EXCEPTION thing results in the script proceeding happily. So far, so good.
The problem appears if I try to do this more than once.
If I run this script to drop tables X and Y:
begin
execute immediate 'drop table X';
exception when others then null;
execute immediate 'drop table Y';
exception when others then null;
end;
I get the following error information:
Error starting at line : 1 in command -
begin
execute immediate 'drop table X';
exception when others then null;
execute immediate 'drop table Y';
exception when others then null;
end;
Error report -
ORA-06550: line 6, column 1:
PLS-00103: Encountered the symbol "EXCEPTION" when expecting one of the following:
( begin case declare end exit for goto if loop mod null
pragma raise return select update when while with
<< continue close current delete fetch lock
insert open rollback savepoint set sql execute commit forall
merge pipe purge
ORA-06550: line 7, column 4:
PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:
end not pragma final instantiable order overriding static
member constructor map
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
What am I missing here? If I removed the second EXCEPTION WHEN statement, the script fails if table Y doesn't exist... I need to catch this error...
I found my answer here:
Two PLSQL statements with begin and end, run fine separately but not together?
Apparently I need
begin
begin
some stuff
end;
begin
some other stuff
end;
end;
or to put a / after each of the two inner block END; 's...
exception when others then null; applies to a single Begin/End block. It's not really valid to have multiples in a single block. You can get around this by having multiple blocks that are individually caught.
begin
execute immediate 'drop table X';
exception when others then null;
end;
/
begin
execute immediate 'drop table Y';
exception when others then null;
end;
/
Related
How can I handle this compilation error through exception?
declare
table_or_view_does_not_exist exception;
pragma exception_init(table_or_view_does_not_exist,-00942);
b exception;
pragma exception_init(b,-00942);
d_table varchar2(200);
c_table varchar2(200);
c_count Number;
begin
begin
d_table:='drop table audit_table PURGE';
execute immediate d_table;
exception
when table_or_view_does_not_exist then
null;
end;
<<lable>>
c_table := 'create table audit_table
(table_name varchar2(50),
column_name varchar2(50),
count_type varchar2(50),
v_count number)';
execute immediate c_table;
select count(*) into c_count from customer_profile where cust_id is null;
insert into audit_table columns (table_name,column_name,count_type,v_count) values('customer_profile','cust_id','null','c_count');
exception
when b then
GOTO lable;
end;
Error report:
ORA-06550: line 25, column 13:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 25, column 1:
PL/SQL: SQL Statement ignored
ORA-06550: line 28, column 2:
PLS-00375: illegal GOTO statement; this GOTO cannot branch to label 'LABLE'
ORA-06550: line 28, column 2:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
What you do, is just bad practice. In Oracle, we don't create tables in PL/SQL but at SQL level and then use them in our procedures.
In your case, you'd
-- create table first
create table audit_table ...;
-- use it in PL/SQL procedure
declare
...
begin
...
insert into audit_table ...
end;
/
You can't "handle" compilation error through exception. What you could do is to put insert statement into dynamic SQL. Also, it wouldn't harm if you used valid syntax (there's no columns "keyword" there).
execute immediate q'[insert into audit_table
(table_name, column_name, count_type, v_count)
values('customer_profile', 'cust_id', 'null', :a)]'
using c_count;
but - once again - that's just bad practice. Don't do it that way, there's no benefit and many disadvantages.
As of goto - well, jumping around your code is almost always wrong. Error you got says that you can't jump out of the exception handler so your idea was wrong anyway.
You actually can trap compilation errors, by wrapping the whole block in an execute immediate so compilation is postponed until runtime. But that's not the right way to go.
Your real issue is that you are referring to the object you are dynamically dropping/creating as a hard-coded dependency of your PL/SQL block:
insert into audit_table ... -- this is a hard dependency
You can't compile PL/SQL if hard dependencies don't exist until run time. If you really must dynamically drop/recreate the table (and I agree with the above reviewer that this is architecturally questionable), you must also make this insert dynamic as well in order to break the dependency chain:
execute immediate 'insert into audit_table values (:col1, :col2, :col3, :col4)' using 'customer_profile','cust_id','null','c_count'; -- this creates no dependency
That will prevent Oracle from creating an object dependency on this table so your code can compile even though the table does not exist at compile time.
But once again, please be sure this is the right technique. There are only a few rare cases where this actually makes good architectural sense. Oh, and please remove the GOTO and label junk. We really don't use that in modern languages; they create unmanageable spaghetti code.
Could you consider another way to control the flow - comments in the code:
DECLARE
cmd VarChar2(200);
c_count Number(6);
--
Status VarChar2(255);
BEGIN
cmd :='drop table audit_table';
Begin -- 1st nested PL/SQL block
Execute Immediate cmd;
Status := 'OK';
Exception -- this could be raised if the table doesn't exist which is probably OK
When OTHERS Then
Status := 'OK'; -- SQLERRM = ORA-00942: table or view does not exist - nothing happens - it would be droped anyway
-- Status := 'ERR - ' || SQLERRM; -- this is alternative if you want to do something else with this ERR
End;
If Status != 'OK' Then -- here you can check the status and decide what to do
GoTo EndIt;
End If;
--
<<lable>> -- in this code there is no need for this label
cmd := 'create table audit_table (table_name varchar2(50), column_name varchar2(50), count_type varchar2(50), v_count number)';
Begin -- 2nd nested PL/SQL block
Execute Immediate cmd;
Status := 'OK';
Exception
When OTHERS Then
Status := 'ERR - ' || SQLERRM;
End;
If Status != 'OK' Then -- here you can check the status and decide what to do
dbms_output.putline(Status); -- send error message and exit - there is no audit_table
GoTo EndIt;
Else
Null; -- here you can do something else (if maybe the table already exists)
End If;
--
-- think about what could go wrong below (2 lines) and either leave it as it is or put it in the 3rd nested PL/SQL block
Select count(*) Into c_count From customer_profile Where cust_id Is Null;
insert into audit_table (table_name, column_name, count_type, v_count) values('customer_profile', 'cust_id', 'Null', c_count);
<<EndIt>> -- used to end the block if needed
Null;
EXCEPTION
When OTHERS Then
dbms_output.putline(SQLERRM); -- You can not get out of here since it is main PL/SQL blok that went into an exception
END;
Addition
Below is an option of basic steps to do the job (3rd block) without compilation errors. I have no possibility to check it out so please do it yourself (there could be some syntax problem or something else). The goal is to be sure that you can insert the record...
I declared sq variable to construct insert command and the end of the code above could be (please check the command yourself - I can't test it right now) something like here:
DECLARE
...
sq VarChar2(1) := ''''; -- this is single-quote character
...
BEGIN
...
...
--
-- think about what could go wrong below (2 lines) and either leave it as it is or put it in the 3rd nested PL/SQL block
Select count(*) Into c_count From customer_profile Where cust_id Is Null;
cmd := 'insert into audit_table (table_name, column_name, count_type, v_count) values(' || sq || 'customer_profile' || sq || ', ' || sq || 'cust_id' || sq || ', ' || sq || 'Null' || sq || ', ' || c_count || ')';
Begin -- 3rd nested PL/SQL block
Select Count(*) Into c_count From all_tables Where table_name = 'audit_table' and owner = 'your_table_owner'; -- check if table exist - if it doesn't set Status
If c_count = 1 Then -- if table exists then you can insert the record
Execute Immediate cmd;
Status := 'OK'; -- all done
Else
Status := 'ERR';
End If;
Exception
When OTHERS Then
Status := 'ERR - ' || SQLERRM; -- catch error and pass it to the main block
End;
If Status != 'OK' Then -- here you can check the status and decide what to do
dbms_output.putline(Status); -- send error message and exit - there is no audit_table -->> OR DO SOMETHING ELSE
GoTo EndIt;
Else
Null; -- here you can do something else if you wish
End If;
--
<<EndIt>> -- used to end the block if needed
Null;
EXCEPTION
When OTHERS Then
dbms_output.putline(SQLERRM); -- You can not get out of here since it is main PL/SQL blok that went into an exception
END;
I have created this guard function:
create or replace
FUNCTION checkTableExists (tableName varchar2)
RETURN BOOLEAN
IS c INT;
BEGIN
SELECT COUNT(*) INTO c FROM user_tables where table_name = upper(tableName);
return c = 1;
END;
I try to use it like:
IF checkTableExists ('NO_TABLE') THEN
DELETE FROM NO_TABLE;
END IF;
Even though the table doesn't exist, I get:
Error report:
ORA-06550: line 6, column 17:
PL/SQL: ORA-00942: table or view does not exist
ORA-06550: line 6, column 5:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
How do I get around this? Dynamic SQL?
UPDATE: I get no error if I run:
IF checkTableExists ('NO_TABLE') THEN
dbms_output.put_line('argh');
END IF;
And argh is not output. If I run the above with a table that does exist, argh is output as expected.
Your statement is parsed completly. So if you replace
DELETE FROM NO_TABLE;
by
null;
your statement should work.
Your error message was a little bit misleading because in line 6 is the select on user_tables and the delete is in line 2 in a different statement. This makes it harder to debug.
So you have to use dynamic sql:
execute immidiate 'delete from ' || 'NO_TABLE';
Don't use a guard function; just try to delete the table and catch the exception if it does not exist:
DECLARE
table_not_exists EXCEPTION;
PRAGMA EXCEPTION_INIT( table_not_exists, -942 );
BEGIN
EXECUTE IMMEDIATE 'DELETE FROM no_table';
DBMS_OUTPUT.PUT_LINE( 'deleted' );
EXCEPTION
WHEN table_not_exists THEN
DBMS_OUTPUT.PUT_LINE( 'did not exist' );
END;
/
db<>fiddle
If you don't want to initialise the exception in lots of different PL/SQL blocks then initialise once it in a package:
CREATE PACKAGE exceptions IS
table_not_exists EXCEPTION;
PRAGMA EXCEPTION_INIT( table_not_exists, -942 );
END;
/
and the code is then simply:
BEGIN
EXECUTE IMMEDIATE 'DELETE FROM no_table';
DBMS_OUTPUT.PUT_LINE( 'deleted' );
EXCEPTION
WHEN EXCEPTIONS.table_not_exists THEN
DBMS_OUTPUT.PUT_LINE( 'did not exist' );
END;
/
db<>fiddle
If you do want to use your guard function (I'd advise that you don't) then just use EXECUTE IMMEDIATE:
IF checkTableExists ('NO_TABLE') THEN
EXECUTE IMMEDIATE 'DELETE FROM NO_TABLE';
END IF;
I'm running the PL/SQL block below from a script. As you can see, it adds a column, and should catch any exceptions - among them, the one that would get thrown in the column already existed.
Now if I run this and the column already exists, I get an error:
Error code: -1735
Error message: ORA-01735: invalid ALTER TABLE option
That is all well and good, but if I run the inner SQL instead though; that is the SQL that follows execute immediate on it's own, I get this message instead, which is more precise:
ORA-01430: column being added already exists in table
The first error has the error code -1735, and I am able to catch with the when-clause that is commented out in the code below; if it is not commented out, the result will instead be:
Some other error occurred
I am not able to catch the -1430 exception though, even though that seems to be the root cause of the exception.
So my question: Is there any way to access this "inner" exception in this case? (is that even a valid term in this case?) In other words, can this be modified so as to provide a more specific error message?
DECLARE
column_exists exception;
pragma exception_init (column_exists , -1430);
general_error exception;
pragma exception_init (general_error , -1735);
BEGIN
execute immediate 'ALTER TABLE my_table
ADD (some_column VARCHAR2(10 CHAR));';
EXCEPTION
-- I expected / wanted this to catch my error in order
-- to let me output a more specific message:
WHEN column_exists THEN
DBMS_OUTPUT.PUT_LINE('The column or index already exists');
-- Note: Commented out, but would otherwise catch the general error.
-- (I tested it here just to confirm that I can catch exceptions this way)
-- WHEN general_error THEN
-- DBMS_OUTPUT.PUT_LINE('Some other error occurred');
-- General catch: Generates the first message quoted above:
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error code: ' || SQLCODE);
DBMS_OUTPUT.PUT_LINE('Error message: ' || SQLERRM);
END;
/
This is what I get after modifying your code a little bit:
In line #9, there's a semi-colon which should be removed as EXECUTE IMMEDIATE doesn't allow it, here:
ADD (some_column VARCHAR2(10 CHAR));';
^
|
remove it
Sample table:
SQL> CREATE TABLE test
2 AS
3 SELECT * FROM dept;
Table created.
Your code:
SQL> DECLARE
2 column_exists EXCEPTION;
3 PRAGMA EXCEPTION_INIT (column_exists, -1430);
4
5 general_error EXCEPTION;
6 PRAGMA EXCEPTION_INIT (general_error, -1735);
7 BEGIN
8 EXECUTE IMMEDIATE 'ALTER TABLE test
9 ADD (loc VARCHAR2(10 CHAR))'; --> remove ; here
10 EXCEPTION
11 -- I expected / wanted this to catch my error in order
12 -- to let me output a more specific message:
13 WHEN column_exists
14 THEN
15 DBMS_OUTPUT.PUT_LINE ('The column or index already exists');
16 -- Note: Commented out, but would otherwise catch the general error.
17 -- (I tested it here just to confirm that I can catch exceptions this way)
18 -- WHEN general_error THEN
19 -- DBMS_OUTPUT.PUT_LINE('Some other error occurred');
20
21 -- General catch: Generates the first message quoted above:
22 WHEN OTHERS
23 THEN
24 DBMS_OUTPUT.PUT_LINE ('Error code: ' || SQLCODE);
25 DBMS_OUTPUT.PUT_LINE ('Error message: ' || SQLERRM);
26 END;
27 /
The column or index already exists
PL/SQL procedure successfully completed.
SQL>
Now, I'm not sure whether it was the case with the superfluous semi-colon or not. Remove it and rephrase the question, if necessary.
I have strange situation
It's bit hard to explain but I'll do my best
There are 3 different database included
From DB1 I call function on DB2 (over dblink)
That procedure calls another procedure that inserts data into table on DB3
Function on DB2 has EXCEPTION handle that should rollback everything that it did in case of exception
I did example run, and everything went well (there was no error) but insert from procedure 3 was not rollbacked and I have to rollback from DB1 to truly rollback
If i commit from db1, row is inserted
Am I doing something wrong and is there a way to rollback directly from function on db2
Here is some example code:
--DB1
PROCEDURE 1
BEGIN
x := function2#dblink_to_db2();
END;
--DB2
FUNCTION 2
BEGIN
procedure3();
RAISE SOME EXCEPTION;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
do_something_else();
RETURN 0;
END;
PROCEDURE 3
BEGIN
INSERT INTO tableA#dblink_to_db3 VALUES ... ;
END;
So no error is raised but insert into table on db3 is not rollbacked
You must be having a commit somewhere in your code either before the raise exception or in Procedure3. I just tested the below code and it rolledback everything before the exception. Please ignore the naming conventions, had to go due to time constraint.
Database 3
CREATE TABLE temp
(col1 NUMBER);
create or replace procedure testp(i number)
as
BEGIN
INSERT INTO temp VALUES (i);
END;
/
Database2
CREATE OR REPLACE FUNCTION DLR_TRANS.testf(i number)
return number
as
e exception;
BEGIN
testp#TO_DB3(i);
RAISE e;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RETURN 0;
END;
/
database1
declare
x number;
BEGIN
x := testf#TO_DB2(15);
DBMS_OUTPUT.PUT_LINE ( 'x = ' || x );
commit;
END;
x returns 0 due to exception in DB2's function.
And below is the data when I ran the below statement on DB3
select * from temp;
Hope this helps
The problem is that you have "handled" the exception in [function 2]. You should not put the exception block in [function 2] at all. And let the exception propagate up to [procedure 1]. Here you will implicitly or explicitly rollback.
If you must have an exception block in [function 2] then you should have a [raise] at the end. Handling the exception like this is saying [I have handled it and for all practical purposes the caller of this should not think anythibg bad has happened]
How can I find the position of an error in a Dynamic SQL statement in PL/SQL or SQL?
From SQL*Plus I see the position of an error in, for example, an invalid SQL DML statement:
SYS#orcl> SELECT
2 X
3 FROM
4 TABLEX
5 /
TABLEX
*
ERROR at line 4:
ORA-00942: table or view does not exist
SQL*Plus shows the error with the line number, and prints and marks that line with an asterisk where the error is found.
Converting to Dynamic SQL, I can get the error code (SQLCODE) and error message (SQLERRM):
SYS#orcl> SET SERVEROUTPUT ON
SYS#orcl> BEGIN
2 EXECUTE IMMEDIATE 'SELECT X FROM TABLEX';
3 EXCEPTION
4 WHEN OTHERS THEN
5 DBMS_OUTPUT.PUT_LINE('SQLCODE:' || SQLCODE);
6 DBMS_OUTPUT.PUT_LINE('SQLERRM:' || SQLERRM);
7 END;
8 /
SQLCODE:-942
SQLERRM:ORA-00942: table or view does not exist
But how do I get the position of the error in the Dynamic SQL string?
I see that Oracle provides a SQL Communications Area (SQLCA) that contains interesting information about an error. In particular:
the SQLCODE and SQLERRM fields (that might be the source of the data retrieved with the respective PL/SQL functions),
the SQLERRD field where the SQLERRD(5) element that gives the 'parse error offset'.
Is it possible to access SQLERRD from PL/SQL or SQL? If so, how? If not, what other technique can give the location of the error from PL/SQL or SQL?
(Here http://docs.oracle.com/cd/B28359_01/appdev.111/b31231/chapter8.htm#BABIGBFF the SQLCA is documented and accessed with Pro*C.)
(The answer here how to declare SQLCA.SQLERRD? seems to indicate that SQLERRD is not defined in PL/SQL and therefore not accessible.)
(The discussion here Why doesn't Oracle tell you WHICH table or view does not exist? gives some suggestions to show bad SQL using trace files and to show the location of errors in some development tools.)
you got a package for extracting error messages in dbms_utility
begin
.. generate error
exception when others then
dbms_output.put_line(
dbms_utility.format_call_stack() || chr(10) ||
dbms_utility.format_error_backtrace() || chr(10) ||
dbms_utility.format_error_stack())
end;
Running the statement through dynamic PL/SQL will store the relevant line number in the error stack.
For example, this statement has an error on line 4:
declare
v_count number;
v_bad_sql varchar2(32767) :=
'SELECT
X
FROM
TABLEX';
begin
execute immediate v_bad_sql into v_count;
exception when others then
begin
execute immediate
'begin for i in ( '||v_bad_sql||') loop null; end loop; end;';
exception when others then
dbms_output.put_line(sqlerrm);
end;
end;
/
ORA-06550: line 4, column 4:
PL/SQL: ORA-00942: table or view does not exist
ORA-00942: table or view does not exist
ORA-06550: line 1, column 18:
PL/SQL: SQL Statement ignored
ORA-00942: table or view does not exist
There are some drawbacks to this method:
It requires some extra, ugly code to catch the exception and re-try the SQL.
The example only works for selects. You'll need to tweak it for insert, update, delelete, merge, dynamic PL/SQL, etc. Normally you should know what kind of SQL statement it is. If you're unlucky, you'll need to parse the statement, which can be very difficult.
The column number is wrong if the entire PL/SQL statement is on one line.