Store the data of deleted record using trigger - oracle

I want to write a trigger which fires on deletion of a record from a table, and inserts a record in another table and uses the details of the record deleted.
Database : Oracle 10g
My trigger looked like this
CREATE or REPLACE TRIGGER myTrigger
AFTER DELETE
ON myTable
REFERENCING NEW AS old_tab
FOR EACH ROW
BEGIN
INSERT INTO ACTIVITYLOG values ('ADMIN',:old_tab.tabletID,'MIGRATION','ERROR','TEST','T','NIL',sysdate)
END;
here :old_tab.tabletID the tabletID is the column of the table myTable in which deletion is done.
I want to save the I and a log that it was deleted.
But when I try deleting a record I get the following error
Error code 4098, SQL state 42000: ORA-04098: trigger 'DB.MYTRIGGER' is
invalid and failed re-validation
P.S. Ran the trigger creation in NetBeans SQL Editor.
Here is the,
EDIT
STRUCTURE OF myTable (Table deletion occurs)
tabletID varchar2(15) PRIMARY KEY
tabletName varchar2(100)
STRUCTURE OF ACTIVITYLOG
username varchar2(15)
tabletKey varchar2(15)
page_ref varchar2(100)
errors varchar2(100)
remarks varchar2(100)
operationcode char(2)
lastupdateip varchar2(20)
lastupdatedate date
Sorry don't have access to SQL PLUS EDITOR.

You should use the :OLD values rather than the :NEW values. The :NEW values in a DELETE trigger (whether BEFORE or AFTER) are blank. This makes sense, because if you think about it the record has logically ceased to exist at this point.
However that is not a source of compilation errors.
"still the same error shows up on deletion. "
I suppose we could spend all day guessing what's wrong so let's stop now. You can discover the compilation errors with this simple query:
select * from user_errors
where name = 'MYTRIGGER'
and type = 'TRIGGER'
"I changed the :NEW to :OLD, and added a semicolan and ran it on SQL
PLUS, and that did the trick"
For the benefit of future here is a version of the trigger which will compile and which will correctly write the required values:
CREATE or REPLACE TRIGGER myTrigger
AFTER DELETE
ON myTable
REFERENCING OLD AS old_tab
FOR EACH ROW
BEGIN
INSERT INTO ACTIVITYLOG values ('ADMIN',:old_tab.tabletID,'MIGRATION','ERROR','TEST','T','NIL',sysdate);
END;
/

The problem is this:
REFERENCING NEW AS old_tab
You've redefined the NEW values with the label "old_tab". This is somewhat like adding #define FALSE TRUE to the top of a program.
Add a semicolon after the insert statement
Because you're using an AFTER DELETE trigger, you only need to access the :OLD values, e.g.:
CREATE or REPLACE TRIGGER myTrigger
AFTER DELETE
ON myTable
FOR EACH ROW
BEGIN
INSERT INTO ACTIVITYLOG values ('ADMIN',:OLD.tabletID,'MIGRATION','ERROR','TEST','T','NIL',sysdate);
END;

Related

Cx_oracle trigger error

I am trying to create a simple trigger in cx_oracle which copies values in a table called Student when some value is inserted in the table studentTemp. There are only two columns in each table namely stud_ID and stud_Name. When I try to insert value in StudentTemp by using
cur.execute("INSERT INTO studentTemp VALUES(1, 'Bob')")
I am getting error DatabaseError: ORA-04098: trigger 'S12345.INSERT_STUD' is invalid and failed re-validation. Following is the code
cur.execute('''CREATE OR REPLACE TRIGGER insert_Stud
AFTER INSERT ON studentTemp
FOR EACH ROW
BEGIN
INSERT INTO Student(Stud_ID,Stud_Name) VALUES
(:new.Stud_ID, :new.Stud_Name);
END;''')
I have also tried
cur.execute('''CREATE OR REPLACE TRIGGER insert_Stud
AFTER INSERT ON studentTemp
REFERENCING NEW AS new
FOR EACH ROW
BEGIN
INSERT INTO Student(Stud_ID,Stud_Name) VALUES (:new.Stud_ID, :new.Stud_Name);
END;/''')
But still get the same error
I can get it work if I use a stored procedure like this
# create insertStudent() stored procedure
cur.execute('''CREATE OR REPLACE PROCEDURE insertStudent(
sID IN STUDENT.STUDENT_ID%TYPE,
sName IN STUDENT.STUDENT_NAME%TYPE)
IS
BEGIN
INSERT INTO STUDENT VALUES(sID, sName);
END;''')
# create insert_Stud() trigger
cur.execute('''CREATE OR REPLACE TRIGGER insert_Stud
AFTER INSERT ON studentTemp
FOR EACH ROW
BEGIN
insertStudent(:new.Stud_ID, :new.Stud_Name);
END;''')
Can someone kindly tell me how to fix this. Thanks
It seems to me like you have an error in naming your Student-Table Fields.
In your Procedure, the colums are referenced as
STUDENT.STUDENT_ID
STUDENT.STUDENT_NAME
In your trigger, you call them
INSERT INTO Student(**Stud_ID**,**Stud_Name**) VALUES ...
Try using SQL*Plus to create the trigger. Then use the "show errors" command to see what the problem is. After that you should be able to use cx_Oracle without any difficulty. The only difference is that the trailing / in SQL*Plus does not need to be there.

Oracle_Trigger: Auto Update Of a Column Based Upon INSERT/UPDATE

This might appear a simple query for most of you, but I am a beginner in Oracle DB.
A table has been created with below script-
CREATE TABLE PLAN_TABLE
(
PL_ID DECIMAL(10,0) PRIMARY KEY NOT NULL
,PL_NAME VARCHAR2(300) DEFAULT NULL
,UPDATED_TS TIMESTAMP DEFAULT SYSDATE NOT NULL
,DELETE_FLAG DECIMAL(10,0) DEFAULT 0 NOT NULL
);
The requirement is to have SYSDATE for UPDATED_TS for any new record inserted into the table and also in case when the DELETE_FLAG is updated to 1. Can it be done by trigger?
The below trigger was created-
CREATE OR REPLACE TRIGGER PT_BEFORE_INSERT_TR
BEFORE INSERT ON PLAN_TABLE
FOR EACH ROW
BEGIN
SELECT SYSDATE INTO :new.UPDATED_TS FROM DUAL;
dbms_output.put_line('Inserted');
END;
/
Below error was encountered while inserting record into the table-
error: ORA-04091: table I60_SCH04.PLAN_TABLE is mutating, trigger/function may not see it
Can you please help in letting me know that where am I committing the mistake? Is there any better way to achieve the requirement based upon INSERT/UPDATE?
The actual error you get is due to the fact that you try to select from a table that you actually are changing. To prevent the issue there are a couple of methods, but in you case things are really simple.
SYSDATE is a function, that you could call directly inside PL/SQL block (which a trigger actually is) and use the value returned to update the set the column value
CREATE OR REPLACE TRIGGER PT_BEFORE_INSERT_TR
BEFORE INSERT ON PLAN_TABLE
FOR EACH ROW
BEGIN
:new.UPDATED_TS := sysdate;
dbms_output.put_line('Inserted');
END;
/
OK, this covers the insert part.
For updating - once again, many options. One could be - change your trigger to BEFORE INSERT OR UPDATE ON PLAN_TABLE.
In this case whenever you issue update or insert - this trigger is fired for each row and updates the date column accordingly.
And of course you could use particular checks available in triggers, something like
IF INSERTING OR UPDATING('DELETE_FLAG') THEN
...
END IF;
and code in the logic you need.

Before-insert trigger gets 'too many rows' error

I have a trigger:
create or replace trigger trig
before insert on sistem
for each row
declare
v_orta number;
begin
SELECT v_orta INTO :new.orta_qiymet
FROM sistem;
v_orta:=(:new.riyaziyyat+:new.fizika)/2;
insert into sistem(orta_qiymet)
values(v_orta);
end trig;
When I insert a row:
insert into sistem(riyaziyyat,fizika) values(4,4)
I get an error:
Why am I getting that error?
This is fundamentally not understanding how triggers work. You can't generally select from the table the trigger is against, and a before-insert trigger shouldn't not insert into the same table again - as that would just cause the trigger to fire again, infinitely (until Oracle notices and stops it). You aren't even currently using the v_orta value you're attempting to query.
I suspect you think the trigger is instead of your original insert perhaps, and really you want to set the orta_qiymet value in the newly-inserted row automatically based on the other two columns you have supplied. To do that you don't (and can't) select those values; instead you refer to the :NEW pseudorecord as you are already doing, and then set the third column value in that same pseudorow:
create or replace trigger trig
before insert on sistem
for each row
begin
:new.orta_qiymet := (:new.riyaziyyat + :new.fizika)/2;
end trig;
/
There is a lot of information in the documentation; this is similar to one of the examples.

Update table column on update related table column in pl sql

I have a database that among the others contains these two tables:
NARUDZBENICA(**SIFANAR**,DATUM,NAZIV,*SIFRADOB,SIFRAKATALOGA,SIFRAZAP,SIFRANACISP*)
DOBAVLJAC(**SIFRADOB**,NAZIV,MAIL,TELEFON,FAKS)
I need a statement trigger to update column 'naziv' in all rows in table NARUDZBENICA where SIFRADOB starts with '0' when I change column 'naziv' on DOBAVLJAC where SIFRADOB is a primary key.
This is what I came up with:
CREATE OR REPLACE TRIGGER "STATEMENT_DOB"
AFTER UPDATE OF NAZIV ON DOBAVLJAC
BEGIN
EXECUTE IMMEDIATE 'ALTER TRIGGER UPDATE_NAR_FRB DISABLE';
UPDATE NARUDZBENICA
SET NAZIV = (SELECT :OLD.NAZIV FROM DOBAVLJAC)
WHERE ROWNUM > 1 AND SIFRADOB = '%0';
EXECUTE IMMEDIATE 'ALTER TRIGGER UPDATE_NAR_FRB ENABLE';
END;
I don't know what you are trying to do disabling and enabling one trigger in another. Otherwise I can see pretty much what you're trying to do, I just don't understand why.
In plain language: when the field NAZIV in table DOBAVLJAC is updated, the old value of the field is saved to the same field in table NARUDZBENICA where the field SIFRADOB begins with the character '0'.
create or replace trigger STATEMENT_DOB
after update of NAZIV on DOBAVLJAC
begin
update NARUDZBENICA
set NAZIV = :old.NAZIV
where SIFRADOB like '0%';
end;
It just occurred to me why you may be disabling the other trigger. It is an update trigger on the other table that watches for that same field to, in turn, propagate the change to DOBAVLJAC. This would create an endless loop of updates. (It would also means you probably should be using the :new.NAZIV value rather than :old.NAZIV.)
There are several tricks to solve that problem. The more involved one is to rename both tables and create views with the original table names. The Instead Of trigger on each view updates the NAZIV changes to both tables. No looping.
That is a rather involved solution. A simpler one (involving fewer object changes) is to create a flag column in both tables. The value of this column is always NULL. When the trigger executes (and it will have to be the before trigger), it checks the NEW value of the flag column. If it is still null, that means this is the first Update so sends an update to the other table. That update sets the NAZIV value to the new value and the flag field to any non-null value. The non-null value tells the other trigger that this is a propagation update so ends the propagation. It changes the :new.flag value to null (you never actually change the contents of the flag field in the table) and just allows the update. This logic would be the same in the trigger on both tables.
This is made a little easier with Oracle 12c and invisible columns. It just allows you to hide the flag field from normal view so people aren't always coming around and asking what it is for.
In reading over my description, I don't think I made my point clearly, especially for non-native English speakers. So here is the trigger code:
create or replace trigger STATEMENT_DOB -- UPDATE_NAR_FRB
before update of NAZIV, Flag on DOBAVLJAC -- NARUDZBENICA
begin
if updating( Flag ) and not updating( NAZIV ) then
-- Someone playing around updating Flag only. Don't allow.
:new.FLAG := null;
elsif :new.FLAG is null then
-- Original Update. Propagate to other table
update NARUDZBENICA -- DOBAVLJAC
set NAZIV = :new.NAZIV,
Flag = 1
where SIFRADOB like '0%';
else
-- This was propagation from other table. Just allow the update of NAZIV
-- but first reset the flag...
:new.FLAG := null;
end if;
end;
That is not quite production-level coding, but I hope it illustrates the idea.
:new or :old identifiers can only be used in row-level triggers. For statement level trigger use of :new and :old identifiers is forbidden.
Also you cannot commit in a trigger. So incase you want to do any DML operation you need to use an autonomous transaction. See below how you can do it.
This trigger will be fired for any change in column value of ID in table A_TABLE.
The new value will be captured and passed on to the anonymous transaction.
CREATE OR REPLACE TRIGGER STATEMENT_DOB
AFTER UPDATE OF ID ON A_TABLE
for each row
BEGIN
proc_upd_tb(:new.id);
END;
Autonomous Transaction:
Data can be updated here and committed as shown.
create or replace procedure proc_upd_tb(id number)
as
PRAGMA AUTONOMOUS_TRANSACTION;
begin
UPDATE AA
SET A = id;
where <condition> ;
commit;
end;

Mutating error on after insert trigger

The below code is giving a mutating error.
Can any1 pls help in solving this.
CREATE OR REPLACE TRIGGER aso_quote_cuhk_trigger
BEFORE INSERT
ON aso.aso_quote_headers_all
FOR EACH ROW
BEGIN
UPDATE aso.aso_quote_headers_all
SET quote_expiration_date=sysdate+90
where quote_header_id=:new.quote_header_id;
END;
/
In oracle there are two levels of triggers: row level and table level.
Row level triggers are executed for each row. Table level triggers executed per statement, even if a statement changed more then one row.
In a row level trigger, you cannot select/update the table itself that has the trigger: you will get a mutating error.
In this case, there is no need for an UPDATE statement. Just try this:
CREATE OR REPLACE TRIGGER aso_quote_cuhk_trigger
BEFORE INSERT
ON aso.aso_quote_headers_all
FOR EACH ROW
BEGIN
:new.quote_expiration_date=sysdate+90;
END;
/
EDIT Rajesh mentioned it is possible, that before inserting a new row, OP wants to update all other records in the aso_quote_headers_all table.
Well, this is feasible, but it's a little tricky. To do this properly, you will need
A pl/sql package and a variable in the package header that is modified by the triggers. This variable could be a list holding the IDs of newly inserted records. Row level after insert trigger would add a new ID to the list. The content of this package variable will be different for each different session, so let's call this variable session_variable.
Row level after insert trigger, that would add new ID to the session_variable.
Table level after insert trigger that would get IDs from the session_variable, process the ID and then remove it from the session_variable. This trigger could execute necessary selects/updates on the aso_quote_headers_all. After a newly inserted ID is processed, this trigger should make sure it gets removed from the session_variable.
I realise you must have resolved your issue by now. However I am adding this answer below to help anyone else facing similar problem as you and I faced.
I recently encountered mutating table (ORA-04091: table XXXX is mutating, trigger/function may not see it) issue and after searching around realised the Compound Triggers feature available in 11g. If you're on 11g following compound trigger would have solved your issue.
CREATE OR REPLACE TRIGGER aso_quote_cuhk_trigger
FOR INSERT ON aso.aso_quote_headers_all
COMPOUND TRIGGER
row_id rowid;
AFTER EACH ROW IS
BEGIN
row_id := :new.rowid;
END AFTER EACH ROW;
AFTER STATEMENT IS
BEGIN
UPDATE aso.aso_quote_headers_all
SET quote_expiration_date = sysdate+90
WHERE rowid = row_id;
END AFTER STATEMENT;
END aso_quote_cuhk_trigger;
/
A word about how it works. This compound trigger fires 2 events :
First is AFTER EACH ROW where we capture the rowid of newly inserted row
Next is AFTER STATEMENT where we update the table using rowid (captured during first event) in the WHERE clause.
A useful link if you want to read more about Compound Triggers.

Resources