Help regarding Triggers - oracle

I have a table (TableA). On this table I am creating a trigger which will insert a row into another table(TableB) for insert, update and delete actions made on TableA. My intention is to track modification on TableA.
I am having single trigger to do this.
(create trigger trig_name before insert or update or delete on TableA... – Kind of).
Now I need what is the actual operation performed on the table A.When the trigger insert a row into TableB, I want actual operation performed on the TableA also to be inserted in a column.
Is there any possibilities to capture what operation performed on TableA with single trigger or Do I have to create separate trigger for each of the DML statement operations?
TIA.

Quoting the docs:
Detecting the DML Operation that Fired a Trigger
If more than one type of DML operation can fire a trigger (for example, ON INSERT OR DELETE OR UPDATE OF emp), the trigger body can use the conditional predicates INSERTING, DELETING, and UPDATING to check which type of statement fire the trigger.
Within the code of the trigger body, you can execute blocks of code depending on the kind of DML operation that fired the trigger:
IF INSERTING THEN ... END IF;
IF UPDATING THEN ... END IF;

You can use the following predicates in the PL/SQL:
IF INSERTING THEN ... END IF;
IF UPDATING THEN ... END IF;
IF DELETING THEN ... END IF;

Related

If a trigger on an Oracle DB table fails to execute successfully or does not compile, can it impact the base table?

let's say there are tables a and b.
There is an AFTER INSERT trigger on a, that copies the row data to b.
If, during the execution of the trigger, there is an error does it impact table a in any way?
If the trigger does not compile, because it's ill-defined, does it impact table a?
I want to add a trigger to a table that is 'not mine'. I want to evaluate the risk that this can potentially pose.
Cheers
============== edit ================
I verified that - by handling the error (as suggested in the reply) - it now does not impact the base table.
create table tableA (column1 number);
create table tableB (column1 number, CONSTRAINT constraintName PRIMARY KEY (column1));
create or replace TRIGGER tableA_trigger
AFTER INSERT ON tableA
FOR EACH ROW
DECLARE
--
BEGIN
insert into tableB values (1);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error occured but ignored.');
END;
insert into tableA values (1);
insert into tableA values (1);
After that, tableA had two records, tableB only 1.
Without the exception handling, both tables would only have one record each, and after the first insert it would have shown an exception in the SQL Developer window.
Yes, it will impact the base table.
If there is error in insert trigger it will not allow to insert any record in base table. Same applies to all type of triggers.
Also, Adding DML triggers to tables affects the performance of DML statements on those tables.
According to oracle documentation:
If a predefined or user-defined error condition or exception is raised
during the execution of a trigger body, then all effects of the
trigger body, as well as the triggering statement, are rolled back
(unless the error is trapped by an exception handler). Therefore, a
trigger body can prevent the execution of the triggering statement by
raising an exception. User-defined exceptions are commonly used in
triggers that enforce complex security authorizations or integrity
constraints.

Fire one trigger when modifying two different tables

We have a problem with our DBMS (Oracle) which prevents us from using materialized views, so my boss came with this idea of implementing it using a real table and triggers; inserting, updating or deleting from this table when an insert, update, or delete is done in one of the tables upon which the materialized view would had been based.
I know I'm going to hell for agreeing to this, but the time for lamentations is well overdue.
My problem is, I don't know how to make this trigger to fire when a change is done in any of these tables, and not in only one of them. Something like this doesn't seem to work:
create trigger my_trigger
after insert or update or delete on table1, table2
Also, would there be a way to create just one trigger instead of one for insert, one for update, and one for delete?
Try this,
CREATE or REPLACE TRIGGER test
AFTER INSERT OR UPDATE OR DELETE ON tabletest
REFERENCING OLD AS OLD NEW AS NEW
FOR EACH ROW
DECLARE
<< Your declarations>>
BEGIN
IF INSERTING THEN
<<Your insertions>>
END IF;
IF UPDATING THEN
<<Your updations>>
END IF;
IF DELETING THEN
<<Your deletions>>
END IF;
EXCEPTION
WHEN OTHERS THEN
<<exception handling>>;
END;
Also you cannot have multiple tables in the same trigger, you need to write the same code and change the table name in each trigger if the functionality is the same.

INSERT trigger for inserting record in same table

I have a trigger that is fire on inserting a new record in table in that i want to insert new record in the same table.
My trigger is :
create or replace trigger inst_table
after insert on test_table referencing new as new old as old
for each row
declare
df_name varchar2(500);
df_desc varchar2(2000);
begin
df_name := :new.name;
df_desc := :new.description;
if inserting then
FOR item IN (SELECT pid FROM tbl2 where pid not in(1))
LOOP
insert into test_table (name,description,pid) values(df_name,df_desc,item.pid);
END LOOP;
end if;
end;
its give a error like
ORA-04091: table TEST_TABLE is mutating, trigger/function may not see it
i think it is preventing me to insert into same table.
so how can i insert this new record in to same table.
Note :- I am using Oracle as database
Mutation happens any time you have a row-level trigger that modifies the table that you're triggering on. The problem, is that Oracle can't know how to behave. You insert a row, the trigger itself inserts a row into the same table, and Oracle gets confused, cause, those inserts into the table due to the trigger, are they subject to the trigger action too?
The solution is a three-step process.
1.) Statement level before trigger that instantiates a package that will keep track of the rows being inserted.
2.) Row-level before or after trigger that saves that row info into the package variables that were instantiated in the previous step.
3.) Statement level after trigger that inserts into the table, all the rows that are saved in the package variable.
An example of this can be found here:
http://asktom.oracle.com/pls/asktom/ASKTOM.download_file?p_file=6551198119097816936
Hope that helps.
I'd say that you should look at any way OTHER than triggers to achieve this. As mentioned in the answer from Mark Bobak, the trigger is inserting a row and then for each row inserted by the trigger, that then needs to call the trigger to insert more rows.
I'd look at either writing a stored procedure to create the insert or just insert via a sub-query rather than by values.
Triggers can be used to solve simple problems but when solving more complicated problems they will just cause headaches.
It would be worth reading through the answers to this duplicate question posted by APC and also these this article from Tom Kyte. BTW, the article is also referenced in the duplicate question but the link is now out of date.
Although after complaining about how bad triggers are, here is another solution.
Maybe you need to look at having two tables. Insert the data into the test_table table as you currently do. But instead of having the trigger insert additional rows into the test_table table, have a detail table with the data. The trigger can then insert all the required rows into the detail table.
You may again encounter the mutating trigger error if you have a delete cascade foreign key relationship between the two tables so it might be best to avoid that.

Is it possible to compare other tables within a trigger?

I have a database with tables that are chained together with foreign keys, and the last one in the chain also has a foreign key to itself. I want to delete them with cascade on, exapt for the last one in the chain. That one should be set null, unless it's parent record has a certain value. I figured i would do that with a trigger: whenever the last table updated, if the foreign key to itself had been set to null, check the field in the parent record, and if it is the value "default", delete the record in the last table.
However, I haven't found any help online indicting that comparing a parent record in another table.
Is this possible?
In general, a row-level trigger on table A cannot query table A. Doing so would generally raise a mutating table exception (ORA-04091). So a trigger is generally not the right solution.
Presumably, you have some sort of API (i.e. a stored procedure) to delete records from the parent table. That API should query this last table before issuing the DELETE against the parent table. It should take care of updating the last table in the chain as well as deleting the data from the parent table.
If you really wanted a trigger-based solution, life would get substantially more complicated. You could work around the mutating table exception by
Creating a package with a collection of primary keys from the parent table
Creating a before statement trigger that initializes this collection
Creating a row-level trigger that populates the collection with the primary keys that were modified by the SQL statement
Creating an after statement trigger that iterates over the collection and issues whatever DML is necessary (unlike row-level triggers, statement-level triggers on table A can query or modify table A).
If you're using 11g, you can simplify this a bit with a compound trigger with before statement, after row, and after statement sections. But you've still got a number of moving pieces to try to coordinate.
AFAIK you won't be able to really delete the record in the last table (mutating table problem), but you could update a status field indicating the record has been logically deleted (untested):
create or replace trigger last_table_trig
before update on last_table
for each row
declare
l_parentField varchar2(100);
begin
if :new.self_ref_fk is null then
select p.parent_field into l_parentField from parent_table p
where p.pk = :new.parent_fk;
if l_parentField = 'default' then
:new.status := 'DELETED';
end if;
end if;
end;

How to prevent an Insert Trigger from being fired when no row is inserted?

I have a TABLE1. On this table I created a trigger : AFTER INSERT OR UPDATE OR DELETE
Now, if I do an insert which doesn't insert anything, the trigger will still be fired :
insert into TABLE1 select * from TABLE1 where 1=0;
This query will insert NO ROWS but yet the trigger is still fired.
Is there a way to avoid this ? Is this normal behavior?
Yes, it is normal behaviour. It can be avoided, though doing so requires having 3 triggers:
A BEFORE trigger to set a package boolean variable to FALSE
A FOR EACH ROW trigger to set the variable to TRUE when a row is inserted
The AFTER trigger you have, where you can now check the value of the variable before carrying out its action.
Sounds like overkill? Maybe it is: what are to trying to achieve with your trigger?
This is the normal behaviour, Oracle is expecting to insert row so it fires both BEFORE INSERT and AFTER INSERT triggers, even if no row is inserted. One of the purpose of the AFTER triggers is to do some action after a statement succeeds. This statement succeeded :)
You could count the number of rows affected with a package variable:
set the mypackage.table1_nb_ins variable to 0 in a BEFORE INSERT trigger
increment the counter in a AFTER INESRT FOR EACH ROW trigger
and then perform your AFTER INSERT logic if your counter is greater than 0
Row Triggers
A row trigger is fired each time the table is affected by the triggering statement. For example, if an UPDATE statement updates multiple rows of a table, a row trigger is fired once for each row affected by the UPDATE statement. If a triggering statement affects no rows, a row trigger is not run.
https://docs.oracle.com/cd/B19306_01/server.102/b14220/triggers.htm

Resources