Why does this Oracle PLSQL function need to be recompiled when a table is updated? - oracle

I have written this function for an Oracle db for a class project:
create or replace Function loginUser ( name_in IN varchar2, pass_in IN varchar2 )
Return Number
IS
cursor c1 is
SELECT u_id
FROM userinfo
WHERE username = name_in AND pass = pass_in AND lockedout='N';
user_id_return number;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
open c1;
fetch c1 into user_id_return;
if c1%notfound then
user_id_return := 0;
INSERT INTO LoginAttempt(username, whenattempted, attempt_status) VALUES (name_in, SYSDATE, 'N');
commit;
ELSE
INSERT INTO LoginAttempt(username, whenattempted, attempt_status) VALUES (name_in, SYSDATE, 'Y');
commit;
END IF;
close c1;
Return user_id_return;
EXCEPTION
WHEN OTHERS THEN
raise_application_error(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);
END;
It works great, I get an insert into a table called LoginAttempt when you call
SELECT loginUser('name','pass') FROM DUAL;
The issue, however, is that new records and updates to userinfo are not reflected in the SELECT statement at the top of the function.
I have to recompile the function each time I update the userinfo table.
Why is this? Is this how functions with SELECT statements work? Is the table that is being SELECTED from compiled when the function is compiled?
Is this related to the PRAGME AUTONOMOUS_TRANSACTION bit?
The schema for the table can be found on github (https://github.com/tmsimont/cs3810schema/blob/master/export.sql)

Because your function executes its DML in an autonomous transaction (i.e. a separate session to the calling one), it cannot see data that has not been committed by the calling session. Until your code commits, the changes it makes are not visible to any other session, including the one used by your function.
This has nothing to do with needing to compile the function. The reason compilation caused it to suddenly see the data is because DDL always issues a commit, thus having the side effect of committing the data you had inserted.

Related

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 can I get the inserted primary key value from AFTER INSERT trigger in Oracle?

My Oracle DB has a table DOC_WF_COMM and its primary key is DWFC_ID. Primary key value is based on a sequence called SQ_DOC_WF_COMM.
I have created a row level AFTER INSERT trigger on that table and inside the trigger I need to join the inserted record with some other tables like this:
create or replace TRIGGER TRG_DOC_WF_COMM_AFT_INS AFTER INSERT ON DOC_WF_COMM REFERENCING OLD AS OLD NEW AS NEW FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
L_SUBJECT VARCHAR2(300);
L_BODY CLOB;
L_PNT_CODE VARCHAR(100) := NULL;
L_DR_PRJ_ID NUMBER(12);
L_STR_EMAIL VARCHAR2(120);
L_DWFC_TO_USR_ID VARCHAR2(12);
L_PNT_ID NUMBER(12);
L_PNT_EMAIL_YN VARCHAR(1);
L_PNT_ACTIVE_YN VARCHAR(1);
L_PNT_NOTIFY_YN VARCHAR(1);
BEGIN
IF INSERTING THEN
L_PNT_CODE := 'WFNT_MESSAGE';
SELECT DR_PRJ_ID, STR_EMAIL, DWFC_TO_USR_ID INTO L_DR_PRJ_ID, L_STR_EMAIL, L_DWFC_TO_USR_ID
FROM DOC_WF_COMM
JOIN DOC_WF_USERS ON DWFU_ID = DWFC_DWFU_ID
JOIN DOC_WORKFLOW ON DWF_ID = DWFU_DWF_ID
JOIN DOCUMENT_REF ON DR_ID = DWF_DR_ID
JOIN ST_REGISTER ON STR_ID = DWFU_STR_ID
WHERE DWFC_ID = :NEW.DWFC_ID AND DWFC_RESPONSE IS NULL;
-- SOME QUERIES HERE
END IF;
END;
The trigger is compiled successfully and when I insert record into DOC_WF_COMM table I get this error:
ORA-01403: no data found ORA-06512
The error is :NEW.DWFC_ID in WHERE clause and I have change it to these values:
:OLD.DWFC_ID
SQ_DOC_WF_COMM.NEXTVAL
SQ_DOC_WF_COMM.CURRVAL
But no any luck. Any idea why this error is and how can I resolve it?
The problem is this line in your trigger:
PRAGMA AUTONOMOUS_TRANSACTION;
That means the trigger executes as an isolated transaction in a separate session, which means it cannot see the uncommitted state of any other session. Crucially this includes the session which fires the trigger, so the autonomous transaction cannot see the record you just inserted. Hence, NO_DATA_FOUND.
You haven't posted the whole trigger or explained what you're trying to do, so only you know why you have included the PRAGMA. However, the chances are you don't need it. Remove the PRAGMA (and the COMMIT) and your trigger should work just fine.
If I understood you correctly, create a local variable and put the next sequence value in there. Then it can be referenced throughout the code, always having the same value. Something like this:
declare
l_seq number := my_seq.nextval;
begin
insert into table_a (id, ...) values (l_seq, ...);
update table_b set id = l_seq where ...
select ... into ... from ... where id = l_seq;
end;
I changed the query inside the trigger to this, it is working fine
SELECT STR_PRJ_ID, STR_EMAIL, :NEW.DWFC_TO_USR_ID INTO L_DR_PRJ_ID, L_STR_EMAIL, L_DWFC_TO_USR_ID
FROM DOC_WF_USERS, ST_REGISTER
WHERE :NEW.DWFC_TO_USR_ID = DWFU_US_ID AND DWFU_STR_ID = STR_ID AND DWFU_ID = :NEW.DWFC_DWFU_ID;
Not sure why is that. If anyone can figure out the mistake in the query given in the question, please let me know. Thanks

pl sql error when working with trigger

I get an error when working with the following trigger:
create or replace trigger t1
after insert or update
on student_tbl
declare
pragma autonomous_transaction;
begin
if inserting then
insert into stud_fees_details(stud_id,fees_balance,total_fees)
select stud_id,course_fees,course_fees from student_tbl s,courses_tbl c where s.stud_standard_id=c.course_id;
elsif updating('stud_standard_id') then
insert into stud_fees_details(stud_id,fees_balance,total_fees)
select stud_id,course_fees,course_fees from student_tbl s,courses_tbl c where s.stud_standard_id=c.course_id;
end if;
end;
error is
ORA-06519: active autonomous transaction detected and rolled back
ORA-06512: at "SYSTEM.T1", line 15
ORA-04088: error during execution of trigger 'SYSTEM.T1'
Database Error Messages
ORA-06519: active autonomous transaction detected and rolled back
Cause: Before returning from an autonomous PL/SQL block, all autonomous transactions started within the block must be completed (either committed or rolled back). If not, the active autonomous transaction is implicitly rolled back and this error is raised.
Action: Ensure that before returning from an autonomous PL/SQL block, any active autonomous transactions are explicitly committed or rolled back.
Example from Database PL/SQL Language Reference
-- Autonomous trigger on emp table:
CREATE OR REPLACE TRIGGER log_sal
BEFORE UPDATE OF salary ON emp FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO log (
log_id,
up_date,
new_sal,
old_sal
)
VALUES (
:old.employee_id,
SYSDATE,
:new.salary,
:old.salary
);
COMMIT;
END;
/
But #a_horse_with_no_name already stated that an autonomous transaction maybe is is not appropriate here.
After removing the autonomous transaction pragma you maybe will run into the problem that #GordonLinoff adresses with his post.
If a trigger doesn't use :new or :old, then it is suspicious. Your trigger is using the same table being modified in queries.
You probably intend:
create or replace trigger t1 after insert or update on student_tbl
declare
pragma autonomous_transaction;
begin
if inserting then
insert into stud_fees_details(stud_id, fees_balance, total_fees)
select stud_id, course_fees, course_fees
from courses_tbl c
where c.course_id = :new.stud_standard_id;
elsif updating('stud_standard_id') then
insert into stud_fees_details(stud_id, fees_balance, total_fees)
select stud_id, course_fees, course_fees
from courses_tbl c
where c.course_id = :new.stud_standard_id;
end if;
commit;
end;
Notes:
The select statements should have table aliases, to distinguish the columns that come from :new and from courses_tbl.
The two clauses to the if look the same to me, so I don't understand the logic.
The join conditions between something called course_id and somethign called stud_standard_id looks suspicious. I would advise you to name foreign keys after the primary key they refer to.

Empty RELIES_ON for RESULT_CACHE

I have a query inside the function with RESULT_CACHE.
So when the table is changed - my cache is invalidated and function is executed again.
What I want is to implement the function that depends only on input parameters, and doesn't depend on any implicit dependencies (like tables, etc).
Is it possible (without dynamic sql)?
a function that depends only on its parameters can be declared DETERMINISTIC. The results of this function will be cached in some cases. This thread on the OTN forums shows how deterministic function results get cached inside SQL statements.
As of 10gR2, the function results don't get cached across SQL statements nor do they get cached in PL/SQL. Still, this cache feature can be useful if you call a function in a SELECT where it might get called lots of time.
I don't have a 11gR2 instance available right now, so I can't test the RESULT_CACHE feature, but have you considered delaring your function relying on a fixed dummy table (a table that never gets updated for instance)?
The correct answer is NO.
A solution in cases where things like result caches and materialized views won't work because of invalidations or too much overhead is the Oracle In-Memory Database Cache option. See result caches ..... what about heavily modified data It's a real smart option, not cheap.
If you use a database link it is possible to create a function result cache that will read from a table when a parameter changes but will not be invalidated when the table changes.
Obviously there are some issues with this approach; performance (even for a self-link), maintenance, the function may return the wrong result, everybody hates database links, etc.
Note that RELIES_ON is deprecated in 11gR2. Dependencies are automatically determined at run-time, even dynamic SQL wouldn't help you here. But apparently this dependency tracking doesn't work over database links.
The script below demonstrates how this works. Remove "#myself" from the function to see how it normally works. Some of the code is based on this great article.
--For testing, create a package that will hold a counter.
create or replace package counter is
procedure reset;
procedure increment;
function get_counter return number;
end;
/
create or replace package body counter as
v_counter number := 0;
procedure reset is begin v_counter := 0; end;
procedure increment is begin v_counter := v_counter + 1; end;
function get_counter return number is begin return v_counter; end;
end;
/
--Create database link
create database link myself connect to <username> identified by "<password>"
using '<connect string>';
drop table test purge;
create table test(a number primary key, b varchar2(100));
insert into test values(1, 'old value1');
insert into test values(2, 'old value2');
commit;
--Cached function that references a table and keeps track of the number of executions.
drop function test_cache;
create or replace function test_cache(p_a number) return varchar2 result_cache is
v_result varchar2(100);
begin
counter.increment;
select b into v_result from test#myself where a = p_a;
return v_result;
end;
/
--Reset
begin
counter.reset;
end;
/
--Start with 0 calls
select counter.get_counter from dual;
--First result is "value 1", is only called once no matter how many times it runs.
select test_cache(1) from dual;
select test_cache(1) from dual;
select test_cache(1) from dual;
select counter.get_counter from dual;
--Call for another parameter, counter only increments by 1.
select test_cache(2) from dual;
select test_cache(2) from dual;
select test_cache(2) from dual;
select counter.get_counter from dual;
--Now change the table. This normally would invalidate the cache.
update test set b = 'new value1' where a = 1;
update test set b = 'new value2' where a = 2;
commit;
--Table was changed, but old values are still used. Counter was not incremented.
select test_cache(1) from dual;
select test_cache(2) from dual;
select counter.get_counter from dual;
--The function is not dependent on the table.
SELECT ro.id AS result_cache_id
, ro.name AS result_name
, do.object_name
FROM v$result_cache_objects ro
, v$result_cache_dependency rd
, dba_objects do
WHERE ro.id = rd.result_id
AND rd.object_no = do.object_id;
Two options:
Don't query any table.
Implement your own cache - wrap the function in a package, and store the query results in a PL/SQL table in memory. The downside to this approach, however, is that the cache only works within a single session. Each session will maintain its own cache.

Simple oracle insert

I am trying to simply insert some information to a table in Oracle using Forms. Sometimes the insert statement works, sometimes it doesn't. I'm just not experienced enough with Oracle to understand what's not working. Here's the code:
PROCEDURE create_account IS
temp_name varchar2(30);
temp_street varchar2(30);
temp_zip number(5);
temp_phone varchar2(30);
temp_login passuse.login%type;
temp_pass varchar2(30);
temp_total number(4);
temp_lgn passuse.lgn%type;
cursor num_cursor is
select MAX(ano)
from accounts;
cursor lgn_cursor is
select MAX(lgn)
from passuse;
BEGIN
temp_name:= Get_Item_Property('ACCOUNTS.A_NAME', database_value);
temp_street:= Get_Item_Property('ACCOUNTS.STREET', database_value);
temp_zip:= Get_Item_Property('ACCOUNTS.ZIP', database_value);
temp_phone:= Get_Item_Property('ACCOUNTS.STREET', database_value);
temp_login:= Get_Item_Property('PASSUSE.LOGIN', database_value);
temp_pass:= Get_Item_Property('PASSUSE.PASS', database_value);
open num_cursor;
fetch num_cursor into temp_total;
open lgn_cursor;
fetch lgn_cursor into temp_lgn;
if(lgn_cursor%found) then
if(num_cursor%found) then
temp_lgn := temp_lgn + 20;
--the trouble maker..
INSERT INTO passuse (lgn, a_type, login, pass)
VALUES (temp_lgn, 1, temp_login, temp_pass);
temp_total := temp_total+1;
INSERT INTO accounts(ano,lgn,a_name,street,zip,phone)
VALUES (temp_total,temp_lgn,temp_name,temp_street,temp_zip,temp_phone);
end if;
end if;
close lgn_cursor;
close num_cursor;
commit;
END;
To expand on #Mikpa's comment - it certainly appears that getting the values for ACCOUNTS.ANO and PASSUSE.LGN from sequences would be a good idea. Populating these fields automatically by using a trigger would also be helpful. Something like the following:
-- Note that the following is intended as a demo. When executing these statements
-- you'll probably have to modify them for your particular circumstances.
SELECT MAX(ANO) INTO nMax_ano FROM ACCOUNTS;
SELECT MAX(LGN) INTO nMax_lgn FROM PASSUSE;
CREATE SEQUENCE ACCOUNTS_SEQ START WITH nMax_ano+1;
CREATE SEQUENCE PASSUSE_SEQ START WITH nMax_lgn+1;
CREATE TRIGGER ACCOUNTS_BI
BEFORE INSERT ON ACCOUNTS
FOR EACH ROW
BEGIN
SELECT ACCOUNTS_SEQ.NEXTVAL
INTO :NEW.ANO
FROM DUAL;
END ACCOUNTS_BI;
CREATE TRIGGER PASSUSE_BI
BEFORE INSERT ON PASSUSE
FOR EACH ROW
BEGIN
SELECT PASSUSE_SEQ.NEXTVAL
INTO :NEW.LGN
FROM DUAL;
END PASSUSE_BI;
Having done the above you can now write your inserts into these tables as
INSERT INTO passuse (a_type, login, pass)
VALUES (1, temp_login, temp_pass)
RETURNING LGN INTO temp_lgn;
and
INSERT INTO accounts(lgn, a_name, street, zip, phone)
VALUES (temp_lgn, temp_name, temp_street, temp_zip, temp_phone);
Note that in the both statements the key values (PASSUSE.LGN and ACCOUNTS.ANO) are not mentioned in the field list as the new triggers should take care of filling them in correctly. Also note that when inserting into PASSUSE the RETURNING clause is used to get back the new value for LGN so it can be used in the insert into the ACCOUNTS table.
Share and enjoy.

Resources