Fire one trigger when modifying two different tables - oracle

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.

Related

Dynamically read the columns of the :NEW object in an oracle trigger

I have an oracle trigger that needs to copy values from the updated table to another table.
The problem is that the columns aren't known when the trigger is created. Part of this system allows the table schema to be updated by the application. (don't ask).
Essentially what I want to do is pivot the table to another table.
I have a stored procedure that will do the pivot, but I can't call it as part of the trigger because it does a select on the table being updated. Causing a "mutating" error.
What would be ideal would be to create a dynamic scripts that reads all the column names from user_tab_cols for the updated table, and reads the value from the :new object.
But of course...I can't :)
:NEW doesn't exist at the point the dynamic script is executed. So something like the following would fail:
EXECUTE IMMEDIATE `insert into pivotTable values(:NEW.' || variableWithColumnName ||')';
So, I'm stuck.
I can't read from the table that was updated, and I can't read the value that was updated from the :NEW object.
Is there anyway to accomplish this other than rebuilding the trigger each time the schema is changed?
No. You'll need to rebuild the trigger whenever the table changes.
If you want to get really involved, you could write a procedure that dynamically generated the DDL to CREATE OR REPLACE the trigger by reading user_tab_columns. You could then create a DDL trigger that fired when the table was altered, submitted a job via dbms_job that called the procedure to recreate the trigger. That works but it's a rather large number of moving parts which means that it can fail in all sorts of subtle and spectacular ways particularly if the application that is making schema changes on the fly decides to add columns in the middle of the day.

How to set different ways to delete in oracle, with and without activating a trigger

I'm using oracle 11g the schema follows as:
TRANSFER(id_transfer, origin_account, destine_account, amount, date, id_replica)
DELETES(id, table, id_replica, date)
Other tables, each have an id_replica as identifier.
I also have a trigger per table, when delete it copies the id_replica deleted in the table DELETES. So if I delete a transfer it first save into DELETES ( id, transfer, id_replica, sysdate), this is for having a record on what I deleted.
The problem is that now I want to make a delete without activating this trigger, I need to have 2 ways of deleting a row, activating the trigger and save what I delete into the table DELETES, and deleting the row without activating this trigger.
Some solutions that came to my mind:
making 2 different procedures/functions to delete a row en each table, (or just one with the trigger the other one can be done as normal).
enabling or disabling the trigger before each delete, this is kind of complicated because its done a lot of times
How do I make a procedure/function to delete a row? Is there other way to do this?

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;

Help regarding Triggers

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;

Resources