update in trigger causing recursive loop when call a function - oracle

I create a table that has a self relation like this :
create table Organization ( id , name , parent_id)
for easy access i add organization binary code to the table , and add a trigger for update the organization binary code after change the parent.
when I want to update then i make recursive loop and take dead lock from oracle.
I update the code in a separate function and call it at the end of trigger;
I make the loop like this:
Update The Record
Run The trigger
Update The Organization Code
Run The trigger
update The Organization Code
and so on

CREATE OR REPLACE TRIGGER TRG_Core_Organization_before
before insert OR UPDATE on Core_Organization
for each row
declare
-- local variables here
parent_HIERARCHICODE number;
PRAGMA AUTONOMOUS_TRANSACTION;
begin
IF INSERTING OR (UPDATING AND :new.parentid != :old.parentid) OR
(UPDATING AND :old.hierarchicode IS NULL) THEN
Begin
-- get last code in this root
-- RAISE_APPLICATION_ERROR(-20001,'ERROR HIERARCHICODE');
select HIERARCHICODE
into parent_HIERARCHICODE
from Core_Organization cp
where cp.id = :new.parentid;
select NVL(max(HIERARCHICODE) + 1, parent_HIERARCHICODE || '001')
into :new.hierarchicode
from Core_Organization cp
where cp.parentid = :new.parentid;
EXCEPTION
WHEN NO_DATA_FOUND THEN
:new.hierarchicode := cast(parent_HIERARCHICODE || '001' as number);
END;
END IF;
end;

Related

Statement level trigger to enforce a constraint

I am trying to implement a statement level trigger to enforce the following "An applicant cannot apply for more than two positions in one day".
I am able to enforce it using a row level trigger (as shown below) but I have no clue how to do so using a statement level trigger when I can't use :NEW or :OLD.
I know there are alternatives to using a trigger but I am revising for my exam that would have a similar question so I would appreciate any help.
CREATE TABLE APPLIES(
anumber NUMBER(6) NOT NULL, /* applicant number */
pnumber NUMBER(8) NOT NULL, /* position number */
appDate DATE NOT NULL, /* application date*/
CONSTRAINT APPLIES_pkey PRIMARY KEY(anumber, pnumber)
);
CREATE OR REPLACE TRIGGER app_trigger
BEFORE INSERT ON APPLIES
FOR EACH ROW
DECLARE
counter NUMBER;
BEGIN
SELECT COUNT(*) INTO counter
FROM APPLIES
WHERE anumber = :NEW.anumber
AND to_char(appDate, 'DD-MON-YYYY') = to_char(:NEW.appDate, 'DD-MON-YYYY');
IF counter = 2 THEN
RAISE_APPLICATION_ERROR(-20001, 'error msg');
END IF;
END;
You're correct that you don't have :OLD and :NEW values - so you need to check the entire table to see if the condition (let's not call it a "constraint", as that term has specific meaning in the sense of a relational database) has been violated:
CREATE OR REPLACE TRIGGER APPLIES_AIU
AFTER INSERT OR UPDATE ON APPLIES
BEGIN
FOR aRow IN (SELECT ANUMBER,
TRUNC(APPDATE) AS APPDATE,
COUNT(*) AS APPLICATION_COUNT
FROM APPLIES
GROUP BY ANUMBER, TRUNC(APPDATE)
HAVING COUNT(*) > 2)
LOOP
-- If we get to here it means we have at least one user who has applied
-- for more than two jobs in a single day.
RAISE_APPLICATION_ERROR(-20002, 'Applicant ' || aRow.ANUMBER ||
' applied for ' || aRow.APPLICATION_COUNT ||
' jobs on ' ||
TO_CHAR(aRow.APPDATE, 'DD-MON-YYYY'));
END LOOP;
END APPLIES_AIU;
It's a good idea to add an index to support this query so it will run efficiently:
CREATE INDEX APPLIES_BIU_INDEX
ON APPLIES(ANUMBER, TRUNC(APPDATE));
dbfiddle here
Best of luck.
Your rule involves more than one row at the same time. So you cannot use a FOR ROW LEVEL trigger: querying on APPLIES as you propose would hurl ORA-04091: table is mutating exception.
So, AFTER statement it is.
CREATE OR REPLACE TRIGGER app_trigger
AFTER INSERT OR UPDATE ON APPLIES
DECLARE
cursor c_cnt is
SELECT 1 INTO counter
FROM APPLIES
group by anumber, trunc(appDate) having count(*) > 2;
dummy number;
BEGIN
open c_cnt;
fetch c_cnt in dummy;
if c_cnt%found then
close c_cnt;
RAISE_APPLICATION_ERROR(-20001, 'error msg');
end if;
close c_cnt;
END;
Obviously, querying the whole table will be inefficient at scale. (One of the reasons why triggers are not recommended for this sort of thing). So this is a situation in which we might want to use a compound trigger (assuming we're on 11g or later).

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

How to declare one variable for each row in trigger?

I have following trigger:
create or replace TRIGGER MY_TIGGER_NAME AFTER UPDATE ON MY_TABLE
REFERENCING OLD AS OLD NEW AS NEW FOR EACH ROW
WHEN ( NEW.STATUS = ANY (10,40,42,44,46,50,60) and OLD.STATUS != NEW.STATUS)
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
IF :NEW.ALERT is NULL
THEN
dbms_alert.signal('print_update_event','update_message');
ELSE
dbms_alert.signal( :NEW.ALERT,'update_message');
END IF;
commit;
END;
I would like to change it because it send alert for each row. I would like to send only one alert if more than one row with ALERT column equal NULL was updated and I would like to send one alert for each row with ALERT column NOT equal NULL.
As I understand Oracle variables I can declare local variable in my trigger but this variable will be declared separately for each row so following change has no sense:
create or replace TRIGGER MY_TIGGER_NAME AFTER UPDATE ON MY_TABLE
REFERENCING OLD AS OLD NEW AS NEW FOR EACH ROW
WHEN ( NEW.STATUS = ANY (10,40,42,44,46,50,60) and OLD.STATUS != NEW.STATUS)
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
flag NUMBER(1,0) :=0;
BEGIN
IF :NEW.ALERT is NULL and flag=0
THEN
dbms_alert.signal('print_update_event','update_message');
flag:=1;
ELSE
dbms_alert.signal( :NEW.ALERT,'update_message');
END IF;
commit;
END;
Package variable could be used instead local variable but I think that package variable is bad idea because two triggers can be executed in parallel. Maybe I am wrong.
I attach diagram of trigger which shows what I would like to achieve.
How to do it?

Inserting multiple records from a trigger in oracle

i created a trigger to insert the employeeIDs whenever a position is updated.
In some cases, there are many employees attached to one position so the trigger isnt able to insert all the employees (only 1). i need all the employee Ids
can any1 help me with this code?
Regards
create or replace trigger postn_updt
after update on postn
for each row
declare
cursor m is
select u.login
from user u,
party_per s
where u.row_id=s.person_id
and s.party_id=:new.row_id;
rownum varchar2(10);
begin
if updating ('postn_type_cd') then
open mult;
fetch mult into rownum;
insert into test123
(employee_number,type,request_date,remarks)
values
(( rownum,
'Updated',sysdate,''
);
close m;
end if;
end;
Triggers != Application code
If you embed application code in trigger like this then it will be horrible to maintain and debug and you'll always be encountering situations where a trigger-based approach won't work because of a mutating table error.
You would do much better to keep triggers for only auditing and other non-application activities, and put this kind of logic in the application itself.
to insert multiple rows you will need either a LOOP or some form of "INSERT... SELECT" statement.
eg.
create or replace trigger postn_updt
after update on postn
for each row
declare
cursor m is
select u.login
from user u,
party_per s
where u.row_id=s.person_id
and s.party_id=:new.row_id;
begin
if updating ('postn_type_cd') then
for mult_rec in m LOOP
insert into test123
(employee_number,type,request_date,remarks)
values
(( mult_rec.login,
'Updated',sysdate,''
);
END LOOP;
end if;
end;
OR
create or replace trigger postn_updt
after update on postn
for each row
declare
begin
if updating ('postn_type_cd') then
insert into test123
(employee_number,type,request_date,remarks)
select u.login ,'Updated',sysdate,''
from user u,
party_per s
where u.row_id=s.person_id
and s.party_id=:new.row_id;
end if;
end;

How to get number of rows affected by a statement when inside that statement's trigger

I have a statement level trigger that fires whenever INSERT UPDATE or DELETE operations are performed on a table (called customers). I want to display a message (to DBMS_OUTPUT) containing the number of rows that were inserted/updated/deleted.
I just want one message for each triggering statement, eg
'4 rows were inserted into customers table'.
How can I access the number of rows that are affected by the triggering statement from INSIDE the trigger declaration, ie XXX in the code below:
CREATE OR REPLACE TRIGGER customer_changes_trigger_2
AFTER INSERT OR UPDATE OR DELETE ON customers
DECLARE
v_operation VARCHAR(10);
v_number_rows NUMBER;
BEGIN
v_number := XXX;
IF INSERTING THEN
v_operation := 'inserted';
END IF;
IF UPDATING THEN
v_operation := 'updated';
END IF;
IF DELETING THEN
v_operation := 'deleted';
END IF;
DBMS_OUTPUT.PUT_LINE
(v_number_rows|| ' rows were ' || v_operation || ' from customers.');
END;
Can't find anything in the documentation, any help appreciated!
One way is to use a global variable to track the number of rows as there is no other way to get the row count from a statement level trigger. You would then need three triggers... one statement level to initialise the variable before the statement is run, one row level to add one to the variable for each row, one statement level to use the row count however you wish. First, set up the variable and a few procedures to help it:
create or replace package PKG_ROWCOUNT is
NUMROWS number;
procedure INIT_ROWCOUNT;
procedure ADD_ONE;
function GET_ROWCOUNT
return number;
end PKG_ROWCOUNT;
/
create or replace package body PKG_ROWCOUNT as
procedure INIT_ROWCOUNT is
begin
NUMROWS := 0;
end;
procedure ADD_ONE is
begin
NUMROWS := Nvl(NUMROWS, 0) + 1;
end;
function GET_ROWCOUNT
return number is
begin
return NUMROWS;
end;
end PKG_ROWCOUNT;
/
The first trigger to initialise the variable:
create or replace trigger CUSTOMER_CHANGES_TRIGGER_1
before insert or update or delete
on CUSTOMERS
begin
PKG_ROWCOUNT.INIT_ROWCOUNT;
end;
The second to update per row:
create or replace trigger CUSTOMER_CHANGES_TRIGGER_2
after insert or update or delete
on CUSTOMERS
for each row
begin
PKG_ROWCOUNT.ADD_ONE;
end;
/
The third to display the total:
create or replace trigger CUSTOMER_CHANGES_TRIGGER_3
after insert or update or delete
on CUSTOMERS
begin
Dbms_output.
PUT_LINE(PKG_ROWCOUNT.GET_ROWCOUNT || ' rows were affected.');
end;
I'm not 100$ sure if it's available inside AFTER trigger body, but you can try examining sql%rowcount

Resources