Link to home
Start Free TrialLog in
Avatar of Julie Kurpa
Julie KurpaFlag for United States of America

asked on

Trigger to record SQL when constraint error encountered

I am trying to get a trigger to work when a user gets a constraint error.    It is supposed to record the SQL being executed by the user.  

The trigger is working fine until I try to combine the multiple lines of the  SQL statement into a variable so that it's all one line.

But when I put in the  line to do the combine, only the first two utl_file.put_line commands performed prior to the loop will work.  All others fail.  

Here is my combine command:      sql_stmt_text := sql_stmt_text || l_text(i);

Here is the trigger:

create or replace trigger test_trigger
after servererror on database

declare

l_text ora_name_list_t;
l_n number;
sql_stmt_text varchar2(32767);
v_output_file1 utl_file.file_type;    

begin

v_output_file1 := UTL_FILE.FOPEN('SQL_STMT_OUTPUT','failing_SQL_Stmt.txt','W');                  


if ( is_servererror(0001) )
then
  utl_file.put_line(v_output_file1,'--------------------');
  utl_file.put_line(v_output_file1,'statement causing error: ');
 
  l_n := ora_sql_txt( l_text );

  for i in 1 .. nvl(l_n,0) loop

    -- this statement works when it is the only one in the loop.
    -- also all following utl_file.put_line commands are successful.
    utl_file.put_line(v_output_file1,l_text(i));

    -- when I include this statement, none of the utl_file.put_line commands work except
    --  for the two just before the loop.  
    sql_stmt_text := sql_stmt_text || l_text(i);

  end loop;

  utl_file.put_line(v_output_file1,sql_stmt_text);

  utl_file.put_line(v_output_file1,'error text: ');

  for i in 1 .. ora_server_error_depth
  loop
    utl_file.put_line(v_output_file1,ora_server_error_msg(i));
  end loop;
    utl_file.put_line(v_output_file1,'--------------------');

end if;

utl_file.fclose_all;

end;
/
Avatar of slightwv (䄆 Netminder)
slightwv (䄆 Netminder)

What version of Oracle are you using?

Works for me using 18.0.

I modified your PUT_LINES to add location1 and location2 tothem so I know what wrote what.

create or replace trigger test_trigger
after servererror on database

declare

l_text ora_name_list_t;
l_n number;
sql_stmt_text varchar2(32767);
v_output_file1 utl_file.file_type;    

begin

v_output_file1 := UTL_FILE.FOPEN('SQL_STMT_OUTPUT','failing_SQL_Stmt.txt','W');                  

if ( is_servererror(0001) )
then
  utl_file.put_line(v_output_file1,'--------------------');
  utl_file.put_line(v_output_file1,'statement causing error: ');
 
  l_n := ora_sql_txt( l_text );


  for i in 1 .. nvl(l_n,0) loop

    -- this statement works when it is the only one in the loop.
    -- also all following utl_file.put_line commands are successful.
    utl_file.put_line(v_output_file1,'Location1: ' || l_text(i));

    -- when I include this statement, none of the utl_file.put_line commands work except
    --  for the two just before the loop.  
    sql_stmt_text := sql_stmt_text || l_text(i);

  end loop;

  utl_file.put_line(v_output_file1,'Location2: ' || sql_stmt_text);

  utl_file.put_line(v_output_file1,'error text: ');

  for i in 1 .. ora_server_error_depth
  loop
    utl_file.put_line(v_output_file1,ora_server_error_msg(i));
  end loop;
    utl_file.put_line(v_output_file1,'--------------------');

end if;

utl_file.fclose_all;

end;
/

show errors

create or replace directory SQL_STMT_OUTPUT as 'C:\TEMP';

drop table tab1 purge;
create table tab1(col1 char(1) primary key);

insert into tab1 values('a');
insert into tab1 values('a');

Open in new window


My test run output:
SQL> @q

Trigger created.

No errors.

Directory created.


Table dropped.


Table created.


1 row created.

insert into tab1 values('a')
*
ERROR at line 1:
ORA-00001: unique constraint (BUD.SYS_C0016150) violated

Open in new window


My file contents:
C:\TEMP>type failing_SQL_Stmt.txt
--------------------
statement causing error:
Location1: insert into tab1 values('a')
Location2: insert into tab1 values('a')
error text:
ORA-00001: unique constraint (MYUSER.SYS_C0016150) violated

--------------------

Open in new window

Avatar of Julie Kurpa

ASKER

Thanks Slightwv.  :)

I'm using Oracle 11g running on Oracle LInux.  

Perhaps it would help if I included the output without the "offending" line and then with it so you see what I see.   Am putting it together now.
In breaking down the changes, I see that it's actually the "utl_file.put_line(v_output_file1,sql_stmt_text);"  that is causing the problem, not the "sql_stmt_text := sql_stmt_text || l_text(i);".

Here is the output WITHOUT the "utl_file.put_line(v_output_file1,sql_stmt_text);"

--------------------
statement causing error:
INSERT INTO myuser.tab1(column1,column2,column3,column4,column5,
column6,column7,column8,column9,column10,column11,column12,colum
m13) VALUES (1111,2222,3333,4444,5555,6666,7777,8888,9999,1010,1
111,1212,1313)
error text:
ORA-00001: unique constraint (MYUSER.PK_TAB1) violated


This is the output when  I include the "utl_file.put_line(v_output_file1,sql_stmt_text);" statement. Notice it doesn't write anything after it's supposed to writethe contents of varable sql_stmt_text:

--------------------
statement causing error:
INSERT INTO myuser.tab1(column1,column2,column3,column4,column5,
column6,column7,column8,column9,column10,column11,column12,colum
m13) VALUES (1111,2222,3333,4444,5555,6666,7777,8888,9999,1010,1
111,1212,1313)


I tried sending the contents of variable sql_stmt_text to different file altogether.  
When doing that, I get the full constraint information written to the first location but never the content of the sql_stmt_text variable in the 2nd file. The 2nd location stays blank.
I can only go with what I can personally test.  The code I posted worked for me.

If you want me to run some other test, post the code you wish me to test.  I don't have access to an 11g database any more but can test on 12.2 right now and 18c later today.

>>Here is the output WITHOUT the "utl_file.put_line(v_output_file1,sql_stmt_text);

If you are getting the output you want, what are you trying to accomplish by changing it?  I'm not understanding why you need the SQL on a single line.  What happens when you get SQL larger than 32k?
It seems your code is exactly like mine so I don't really have any changes to make.  If it runs in 12.2 then I'm not sure what else to try.


I wanted to get the SQL into a single line because I plan to extract certain elements from it and use it in a procedure.  If it's wrapping like it is, then certain elements I need may be split between lines.
OK, I think I've been able to reproduce this using longer SQL.  Let me dig into this a little more.
ASKER CERTIFIED SOLUTION
Avatar of slightwv (䄆 Netminder)
slightwv (䄆 Netminder)

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I really appreciate your help.

On a whim, I directed that variable to a table instead of to a file and it seems to work.  The entire SQL is being written into one column.  This is where I eventually wanted to end up but was just writting to the file so I could see what I was getting.

Here's the adjusted code.  

create or replace trigger test_trigger
after servererror on database

declare

l_text ora_name_list_t;
l_n number;
sql_stmt_text varchar2(32767);
v_output_file1 utl_file.file_type;    

begin

v_output_file1 := UTL_FILE.FOPEN('SQL_STMT_OUTPUT','failing_SQL_Stmt.txt','W');                  


if ( is_servererror(0001) )
then
  utl_file.put_line(v_output_file1,'--------------------');
  utl_file.put_line(v_output_file1,'statement causing error: ');
 
  l_n := ora_sql_txt( l_text );

  for i in 1 .. nvl(l_n,0) loop

    utl_file.put_line(v_output_file1,l_text(i));

    sql_stmt_text := sql_stmt_text || l_text(i);

  end loop;

  INSERT INTO myuser.sql_event VALUES (sql_stmt_text);

  utl_file.put_line(v_output_file1,'error text: ');

  for i in 1 .. ora_server_error_depth
  loop
    utl_file.put_line(v_output_file1,ora_server_error_msg(i));
  end loop;
    utl_file.put_line(v_output_file1,'--------------------');

end if;

utl_file.fclose_all;

end;
/
oh I just saw your post.  am reading it now.
That worked great slighwv!  You are wonderful!

I set the FOPEN size to 32767 and I'm getting the full SQL text on one line in the file.
Now I have many options on how to handle the rest of my project.
Here's the working product with the writing of the SQL on all one line to the output file outside the loop.

create or replace trigger test_trigger
after servererror on database

declare

l_text ora_name_list_t;
l_n number;
sql_stmt_text varchar2(10000);
v_output_file1 utl_file.file_type;    

begin

v_output_file1 := UTL_FILE.FOPEN('SQL_STMT_OUTPUT','failing_SQL_Stmt.txt','W',32767);                  


if ( is_servererror(0001) )
then
  utl_file.put_line(v_output_file1,'--------------------');
  utl_file.put_line(v_output_file1,'statement causing error: ');
 
  l_n := ora_sql_txt( l_text );

  for i in 1 .. nvl(l_n,0) loop

    utl_file.put_line(v_output_file1,l_text(i));

    sql_stmt_text := sql_stmt_text || l_text(i);

  end loop;

  -- write the combined SQL  to a table
  INSERT INTO myuser.sql_event VALUES (sql_stmt_text);

  -- write the combined SQL to the file
  utl_file.put_line(v_output_file1,'error text: ');

  for i in 1 .. ora_server_error_depth
  loop
    utl_file.put_line(v_output_file1,ora_server_error_msg(i));
  end loop;
    utl_file.put_line(v_output_file1,'--------------------');

end if;

utl_file.fclose_all;

end;
/
>>SQL on all one line to the output file outside the loop.

Why when it isn't necessary?
Just to show it as part of the solution.  I won't continue to do it now that I'm writing it to a table.
slightwv didn't have to do further research when his SQL worked fine.  Went the extra mile to reproduce the problem and found the solution.