Find bad dynamic SQL statements containing syntax errors in Oracle - oracle

I was wondering if there is a way to find dynamic SQL statements whose execution broke during runtime in case there was no proper exception handling in the PL/SQL program unit that called the dynanic SQL.
procedure will_crash is
begin
-- 1000 dynamic sql statements here ..
execute immediate 'updaXte dual set X = ''Z'' ' ;
-- ... and 1000 more dynamic sql statements here ..
commit;
end; --> NO proper exception handling for logging the last sql statement
In case a nighttime scheduled program unit contains hundreds of dynamic sql statements, I'd like to find out which one broke without debugging. Does Oracle log anything in its system views?

In you're example there's no need at all to use dynamic SQL, so the easiest solution is to rewrite it as
procedure will_crash is
begin
-- 1000 dynamic sql statements here ..
updaXte dual set X = ''Z''; -- Error at compile time!!
-- ... and 1000 more dynamic sql statements here ..
commit;
end; --> NO proper exception handling for logging the last sql statement
If the real procedure uses dynamic SQL because the SQL statements are saved elsewhere (variables or SQL table), and we're talking only about syntax error, you can achieve your objective before executing statements using DBMS_SQL package.
Here's an example:
DECLARE
lc_good VARCHAR2(200) := 'SELECT 1 FROM dual';
lc_bad VARCHAR2(200) := 'SEL1ECT a FROM dual';
lc_cursor NUMBER;
BEGIN
lc_cursor := SYS.DBMS_SQL.OPEN_CURSOR;
BEGIN
SYS.DBMS_SQL.PARSE(lc_cursor, lc_good, DBMS_SQL.V7);
dbms_output.put_line('Statement 1 is GOOD');
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('Statement 1 is BAD: ' || SQLERRM);
END;
BEGIN
SYS.DBMS_SQL.PARSE(lc_cursor, lc_bad, DBMS_SQL.V7);
dbms_output.put_line('Statement 2 is GOOD');
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('Statement 2 is BAD: ' || SQLERRM);
END;
SYS.DBMS_SQL.close_cursor(lc_cursor);
END;
The function output is the following:
Statement 1 is GOOD
Statement 2 is BAD: ORA-00900: ...
Note that the code only parses the SQL statements, it don't executes them.

Related

migrate raiserror from SYBASE ASE to ORACLE

I am migrating stored procedures to pl/sql blocks, and I have little knowledge in error handling in oracle and nothing in sybase can you help me.
example: sql SYBASE
DELETE table_1
WHERE N=0
SELECT #myrowcount = ##rowcount, #myerror = ##error, #mystat = ##sqlstatus
if (#myerror <> 0)
begin
raiserror 40900 "Error: When Generating Exception List #table_1 (error= %1!)", #mystat
select #cod_err= 1
return #cod_err
end
Edit: sql oracle i dont know if this is right
begin
DELETE table_1
WHERE N=0
EXCEPTION WHEN OTHERS THEN
SWV_error := SQLCODE;
end;
v_mi_error := SWV_error;
if v_mi_error != 0 then
RAISE_APPLICATION_ERROR(-40900,'Error: When Generating Exception List table_1');
return;
end if;
Which error do you expect for delete? It'll either delete some rows, or won't. If table (or column) doesn't exist, code wouldn't even compile so you wouldn't reach runtime error.
Anyway: in Oracle, it looks like this:
begin
delete table_1 where n = 0;
exception
when others then
raise_application_error(-20000, 'Error: ' || sqlerrm);
end;
/
others is exception which handles various things; you don't really care which error it is, as could be any error. Oracle reserved error codes from -20000 to -20999 for us, developers so you have to pick one of these (which means that -40900 won't work).
sqlerrm is error message (its description). If you want, you can get its code via sqlcode.
Example which shows how it actually works (not with delete, though) is query that fetches employee name for non-existent employee number. That raises predefined no_data_found error (whose code is -01403) so you could handle it, directly, or - as my previous example shows - use others.
SQL> declare
2 l_name varchar2(10);
3 begin
4 select ename
5 into l_name
6 from emp
7 where empno = -1;
8 exception
9 when others then
10 raise_application_error(-20000, 'Error: ' || sqlcode ||': '|| sqlerrm);
11 end;
12 /
declare
*
ERROR at line 1:
ORA-20000: Error: 100: ORA-01403: no data found
ORA-06512: at line 10
SQL>

how to insert the result of dbms_output.put_line to a table for error catch?

Exception
WHEN OTHERS THEN
--dbms_output.put_line('pl_update_sidm_user r: ERROR CODE:' || sqlcode || '~' ||
--sqlerrm || ' EMPL_NBR:' || r.EMPL_NBR);
insert into ERROR_MSG (ERROR_MSG_ID,ERROR_MSG) values (ERROR_MSG_ID_SEQ.NEXTVAL, 'pl_update_sidm_user_duty_role r2');
END;
I would like to put the error result to a table.
However, how can I do that?
Can I put the result of dbms_output to a table as a string?
If not, can I get the sqlcode,sqlerrm without using dbms_output?
Thank you !!
From the documentation,
A SQL statement cannot invoke SQLCODE or SQLERRM. To use their values
in a SQL statement, assign them to local variables first
Also,
Oracle recommends using DBMS_UTILITY.FORMAT_ERROR_STACK except when using the FORALL statement with its SAVE EXCEPTIONS clause
So, for SQLCODE or SQLERRM, you should assign them into variables and use them.
DECLARE
v_errcode NUMBER;
v_errmsg VARCHAR2(1000);
BEGIN
--some other statements that may raise exception.
EXCEPTION
WHEN OTHERS THEN
v_errcode := SQLCODE;
v_errmsg := SQLERRM;
insert into ERROR_TABLE (ERROR_MSG_ID,ERROR_MSG) --change your table name
values (ERROR_MSG_ID_SEQ.NEXTVAL,
v_errcode||':'||v_errmsg);
END;
/
Preferably use insert like this instead, as per Oracle's recommendation.
insert into ERROR_TABLE (ERROR_MSG_ID,ERROR_MSG) values (ERROR_MSG_ID_SEQ.NEXTVAL,
DBMS_UTILITY.FORMAT_ERROR_STACK);
Demo
Technically what others are suggesting is correct: the "insert" operation executed in the "exception when others" block will actually insert a new row in the log table.
the problem is that such insert statement will be part of the same transaction of the main procedure and, since you had an error while executing it, you are very likely to rollback that transaction, and this will rollback also the insert in your log table
I suppose the problem you are facing is not that you aren't successfully logging the error message: it is that you are rolling it back immediately afterwards, along with all the other writes you did in the same transaction.
Oracle gives you a way of executing code in a SEPARATE transaction, by using "autonomous transaction" procedures.
you need to create such a procedure:
create or replace procedure Write_Error_log(
arg_error_code number,
arg_error_msg varchar2,
arg_error_backtrace varchar2) is
PRAGMA AUTONOMOUS_TRANSACTION;
begin
INSERT INTO error_msg (
error_msg_id,
error_code,
error_msg,
error_stack)
VALUES (
error_msg_id_seq.NEXTVAL,
arg_error_code,
arg_error_msg,
arg_error_backtrace);
commit; -- you have to commit or rollback always, before exiting a
-- pragma autonomous_transaction procedure
end;
What this procedure does is to write a new record in the log table using a totally separate and independent transaction: the data will stay in the log table even if you execute a roll back in your calling procedure. You can also use such a procedure to create a generic log (not only errors).
All you have to do now is to call the procedure above whenever you need to log something, so your code becomes:
DECLARE
v_errcode NUMBER;
v_errmsg VARCHAR2(1000);
BEGIN
--some other statements that may raise exception.
EXCEPTION WHEN OTHERS THEN
Write_Error_log(SQLCODE, SQLERRM, dbms_utility.format_error_backtrace);
END;
/
P.S: there might be some typos in my code: I can't test it right now since I can't reach an oracle server in this moment.

how to dynamically purge the tables in AQADMIN

When I was trying to purge the QUEUE tables dynamically, I'm getting an error.Below is the code
set serveroutput on;
declare
l_stmt varchar2(2000):='';
po_t dbms_aqadm.aq$_purge_options_t;
BEGIN
for i in (select NAME,queue_table from all_Queues where owner='AQADMIN') loop
--dbms_output.put_line(i.queue_table);
l_stmt:='DBMS_AQADM.PURGE_QUEUE_TABLE ('''||i.queue_table||''',''trunc(enq_time)<sysdate-90'',block => po_t)';
execute immediate l_stmt;
--dbms_output.put_line(l_stmt);
commit;
end loop;
END;
/
Error message:
Error report -
ORA-00900: invalid SQL statement
ORA-06512: at line 8
00900. 00000 - "invalid SQL statement"
*Cause:
*Action:
To know if my query is framing correctly, I commented out the execute immediate part and uncommented the DBMS_OUTPUT command, and the result was posted in sql developer and it executed perfectly, (below is the code output) .so the query is correct, not sure what was wrong with my code,
Output:
anonymous block completed
DBMS_AQADM.PURGE_QUEUE_TABLE ('HEARTBEAT_MSG_QT','trunc(enq_time)<sysdate-90',block => po_t)
then I executed the above output in separate SQL block and it ran fine.Below
declare
po_t dbms_aqadm.aq$_purge_options_t;
begin
DBMS_AQADM.PURGE_QUEUE_TABLE ('HEARTBEAT_MSG_QT','trunc(enq_time)<sysdate-90',po_t);
end;
/
anonymous block completed
When you call execute immediate it is expecting dynamic SQL, not PL/SQL. To run a PL/SQL command you need to wrap it in an anonymous block, just as you did when you ran it manually:
l_stmt:='DECLARE po_t dbms_aqadm.aq$_purge_options_t; BEGIN DBMS_AQADM.PURGE_QUEUE_TABLE ('''||i.queue_table||''',''trunc(enq_time)<sysdate-90'',block => po_t); END;';
or split onto multiple line to make it slightly easier to read:
l_stmt:='DECLARE po_t dbms_aqadm.aq$_purge_options_t;'
|| 'BEGIN '
|| 'DBMS_AQADM.PURGE_QUEUE_TABLE ('''||i.queue_table||''','
|| '''trunc(enq_time)<sysdate-90'',block => po_t);'
|| 'END;';
Notice that you have to re-declare po_t inside that block as that the original declaration is out of scope for the dynamic SQL call (actually, the original one is now redundant...).
You don't need to use dynamic SQL though, you can pass the cursor value straight to the procedure:
DECLARE
l_po dbms_aqadm.aq$_purge_options_t;
BEGIN
for i in (select NAME,queue_table from all_Queues where owner='AQADMIN')
loop
DBMS_AQADM.PURGE_QUEUE_TABLE (queue_table => i.queue_table,
purge_condition => 'trunc(enq_time)<sysdate-90'
purge_options=> l_po);
end loop;
END;
/
I've also removed the commit as it's redundant; from the docs:
This procedure commits batches of messages in autonomous transactions.

ORA-00942: table or view does not exist ... can I get more details?

I am running a pretty complex and long query with hundreds of unions and other complex inner queries ... and I am getting ORA-00942: table or view does not exist .... do I have a way to know which table exactly that doesn't exist ?
N.B. : the query is a part of a PL/SQL procedure and the exception is printed via DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM, 1, 400));
If the PL/SQL procedure compiles, any directly referenced table must exist. I'd assume it is dynamic SQL. If you are using DBMS_SQL, then you can use LAST_ERROR_POSITION. If you are using EXECUTE IMMEDIATE, and you have your SQL in a handy variable, then have your code do something like:
begin
execute immediate v_sql;
exception
when others then
declare
v_cur BINARY_INTEGER;
begin
v_cur := dbms_sql.open_cursor;
dbms_sql.parse (v_cur, v_sql, dbms_sql.native);
exception
when others then
dbms_output.put_line (sqlerrm || ' near pos ' ||
substr(v_sql,dbms_sql.last_error_position -10,40));
dbms_sql.close_cursor (v_cur);
raise;
end;
end;

"Select ... into .." statement fails with "table or view does not exist" even though it should not be run

I have the following PL/SQL script that I'm trying to run through SQL*Plus:
DECLARE
table_exists number;
sequence_exists number;
sequence_start number;
BEGIN
select count(*) into sequence_exists
from all_sequences
where sequence_name='DBSEQ';
select count(*) into table_exists
from dba_tables
where table_name='DBTABLE';
IF sequence_exists = 0 AND table_exists > 0 THEN
select MAX(ID) + 1 into sequence_start from DBTABLE;
ELSE
sequence_start := 1;
END;
IF sequence_exists = 0 THEN
execute immediate 'CREATE SEQUENCE DBSEQ
start with ' || sequence_start || '
increment by 1
nomaxvalue';
END IF;
END;
(this is somewhat minimized, to show the parts that are actually failing).
The problem is that the row
select MAX(ID) + 1 into sequence_start from DBTABLE;
fails with "PL/SQL: ORA-00942: table or view does not exist", because the table does not exist (and I've already verified that the table_exists variable is actually 0). So basically, the select runs (or at least fails) even though the if-clause does not execute. I've also verified that the if-clause indeed fails by replacing the select with e.g. creating a table, which does not exist after running the script.
I did also try catching exceptions instead, doing something like this:
BEGIN
select MAX(ID) + 1 into sequence_start from DBTABLE;
EXCEPTION
WHEN OTHERS THEN
sequence_start := 1;
END;
but that produced the same error.
So, is there anything special with the select statement, that makes it run before anything else? And how should I solve my problem?
The problem is that Oracle has to compile the block before it can run it. Part of compiling the block involves resolving all the static references. If you have a reference to a table that does not exist, the block will fail to compile so it cannot be run. Your exception handler doesn't do anything because it is a compilation error, not an execution error.
If you want your block to refer to a table that may not exist at the time the block is compiled, you would need to use dynamic SQL.
EXECUTE IMMEDIATE 'select max(id) from dbtable'
INTO sequence_start;
That will allow the block to compile successfully since Oracle doesn't need to resolve references in dynamic SQL statements. If your code tries to execute the dynamic SQL statement when dbtable doesn't exist, you'll get a run-time error (which you could catch with an exception handler).

Resources