Creating a back up table trigger in Oracle - oracle

I have a table A which is constantly updated ( insert statements ) by one application. I want to create another table B which consists only few columns of table A. For this I thought of creating a trigger which triggered after insert on Table A, but I don't know how should I write the insert statement inside the trigger. I am not a database expert, so may be I am missing something simple. Please help.
Here is my trigger code:
CREATE OR REPLACE TRIGGER "MSG_INSERT_TRIGGER"
AFTER
insert on "ACTIVEMQ_MSGS"
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
begin
execute immediate 'truncate table MSG_BACKUP';
insert into MSG_BACKUP select * from ACTIVEMQ_MSGS;
COMMIT;
end;

This does not seem to be good idea: every time a new record gets inserted into Table A, delete everything from table B and copy all records from table A. This will be a huge performance issue, once there is many records in table A.
Wouldn't it be enough to create a view on the desired columns of table A?
If not, that is, you still want to "log" all inserts into an other table, then here you go. (I suppose you want to copy the following fields: f1, f2, f3)
CREATE OR REPLACE TRIGGER TR_AI_ACTIVEMQ_MSGS
AFTER INSERT ON ACTIVEMQ_MSGS
FOR EACH ROW
BEGIN
INSERT INTO MSG_BACKUP (f1, f2, f3)
VALUES (:new.f1, :new.f2, :new.f3);
END TR_AI_ACTIVEMQ_MSGS;

Inside an Oracle trigger the record values are available in de :new pseudo record. Your insert statement may look like this:
insert into B (column1, column2)
values (:new.tableA_col1,:new.tableA_col2)

Related

Mutating table exception is not occurring

I have created a trigger and was expecting a mutating table error in below case but didn't get one through normal insert but getting an error while inserting using a query. I am not sure which concept I am missing here.
drop table temp;
create table temp (id number,name varchar2(500),is_active number);
create or replace trigger temp_trg before insert on temp
for each row
declare
v_count number;
begin
select count(1) into v_count from temp;
update temp set is_active=0 where is_active=1 and id=:new.id;
end;
/
select * from temp;
insert into temp values (1,'xyz',1);
insert into temp values (1,'xyz',1);
insert into temp select 1,'xyz',1 from dual;
getting an error while inserting using a query.
Mutating table occurs when we query the table which owns the trigger. Specifically it happens when Oracle can't guarantee the outcome of the query. Now when you insert a single row into the table Oracle can predict the outcome of the query when the FOR EACH ROW trigger fires, because it's a single row.
But with an INSERT FROM query Oracle is confused: should the count be the final figure including all the rows selected by the query or just a rolling count? The answer seems straightforward in this case but it's easy to imagine other queries where the answer is not clear cut. Rather than evaluate each query on its predictability Oracle enforces a plain fiat and hurls ORA-04091 for all query driven inserts.
The restriction on Mutating Tables applies to all triggers that use FOR EACH ROW clause except when either of the following is true:
the trigger fires on BEFORE event, i.e. the data wasn't actually changed
it is known that only one row will be affected - INSERT ... VALUES is the only DML that meets this condition

trigger performance over different cases

I have a table that collects logs of every check_in. I want to pick 4 columns to later process them. I have placed a trigger on every insert and columns are copied. I am confused over which way to do it. For best PERFORMANCE. Below are some different ways I figured out. Assuming my destination table is indexed.
ALTER trigger GetCheckIn_HA_Transit
on tableXyz
AFTER INSERT
as
declare #cardName nvarchar(max)
declare #checkIn datetime
declare #direction varchar(30)
declare #terminal varchar(30)
select
#direction = str_direction,
#terminal = TERMINAL,
#cardName = card_number,
#checkIn = transit_date
from tableXyz
GO
INSERT INTO logs(direction,terminal,cardName,checkIn)
VALUES
(#direction,#terminal,#cardName,#checkIn)
end
Another way i found was without declaration
ALTER trigger GetCheckIn_HA_Transit
on tableXyz
AFTER INSERT
as
GO
INSERT INTO
logs(direction,terminal,cardName,checkIn)
SELECT(STR_DIRECTION,TERMINAL,card_number,transit_date)FROM tableXyz
And is copying data from one table to another table is better in terms of performance than to copy from one database table to another database table over same server ??
Assuming every second we have an insert that will trigger our Trigger.

Reading current table id during trigger operation

I have three tables: A and B
The relationship is A can have many B
So B has a reference to the A.id as one of its columns
Table A
|id|date|...
Table B
|id|A_id|...
I have created an Oracle trigger on table A so that when it is updated it updates an A_Mod table.
This trigger is
CREATE OR REPLACE TRIGGER TR_A_INSERT_UPDATE
AFTER INSERT OR UPDATE ON A
FOR EACH ROW
BEGIN
INSERT INTO A_Mod values(..., :new.date, ...)
END;
This works fine :)
My problem si creating the trigger for table B.
The trigger is:
CREATE OR REPLACE TRIGGER TR_B_INSERT_UPDATE
AFTER INSERT OR UPDATE ON B
FOR EACH ROW
DECLARE
ts TIMESTAMP;
BEGIN
SELECT aa.date INTO ts FROM B bb
INNER JOIN A aa ON a.id = bb.A_id
WHERE bb.id = :new.id;
INSERT INTO A_Mod values(..., :new.date, ...)
END;
This trigger is reading the ID of the updated line in table B and then getting the date from the corresponding row in table A. It then tries to insert it into A_Mod
The problem is I get a mutating error
Error report:
SQL Error: ORA-04091: table B is mutating, trigger/function may not see it
ORA-06512: at "TR_B_INSERT_UPDATE", line 5
ORA-04088: error during execution of trigger 'TR_B_INSERT_UPDATE'
04091. 00000 - "table %s.%s is mutating, trigger/function may not see it"
*Cause: A trigger (or a user defined plsql function that is referenced in
this statement) attempted to look at (or modify) a table that was
in the middle of being modified by the statement which fired it.
*Action: Rewrite the trigger (or function) so it does not read that table.
Looking at the docs I can remove this error by removing the FOR EACH ROW line and having the trigger fire once per statement rather than once per row. Unfortunately I am using an ORM mapper so do not control how the updates happen. I think there would be times when the update could cover multiple rows.
The docs say something about creating a temporary table but I am not sure how this would help. Would I have to create a temporary table inside the trigger, create a trigger on this temporary table which then updates A_Mod, update this temporary table when the trigger is fired and then delete everything after?
Any tips greatly appreciated.
Thanks
It doesn't appear that there is any reason in your trigger on B to query the B table. You should be able to simply query A using the :new.a_id
CREATE OR REPLACE TRIGGER TR_B_INSERT_UPDATE
AFTER INSERT OR UPDATE ON B
FOR EACH ROW
DECLARE
ts TIMESTAMP;
BEGIN
SELECT a.date
INTO ts
FROM A a
WHERE a.id = :new.a_id;
INSERT INTO A_Mod values(..., :new.date, ...)
END;
From a data modeling standpoint, however, I would be very concerned if a trigger on a child table needed to query any information from the parent table or to insert data into the same history table that the parent table writes to. That seems likely to indicate a normalization issue.

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.

Insert into oracle database

Hi I have a database with loads of columns and I want to insert couple of records for testing, now in order to insert something into that database I'd have to write large query .. is it possible to do something like this
INSERT INTO table (SELECT FROM table WHERE id='5') .. I try to insert the row with ID 5 but I think this will create a problem because it will try to duplicate a record, is it possible to change this ID 5 to let say 1000 then I'd be able to insert data without writing complex query and while doing so avoiding replication of data .. tnx
In PL/SQL you can do something like this:
declare
l_rec table%rowtype;
begin
select * into l_rec from table where id='5';
l_rec.id := 1000;
insert into table values l_rec;
end;
If you have a trigger on the table to handle the primary key from a sequence (:NEW.id = seq_sequence.NEXTVAL) then you should be able to do:
INSERT INTO table
(SELECT columns_needed FROM table WHERE whatever)
This will allow you to add in many rows at one (the number being limited by the WHERE clause). You'll need to select the columns that are required by the table to be not null or not having default values. Beware of any unique constraints as well.
Otherwise you'll be looking at PL/SQL or some other form of script to insert multiple rows.
For each column that has no default value or you want to insert the values other than default, you will need to provide the explicit name and value.
You only can use an implicit list (*) if you want to select all columns and insert them as they are.
Since you are changing the PRIMARY KEY, you need to enumerate.
However, you can create a before update trigger and change the value of the PRIMARY KEY in this trigger.
Note that the trigger cannot reference the table itself, so you will need to provide some other way to get the unique number (like a sequence):
CREATE TRIGGER trg_mytable_bi BEFORE INSERT ON mytable FOR EACH ROW
BEGIN
:NEW.id := s_mytable.nextval;
END;
This way you can use the asterisk but it will always replace the value of the PRIMARY KEY.

Resources