Is possible to create or replace a view inside a trigger in Oracle?
The view is created by joining 2 tables and one of them is the one updated by the trigger
Just to provide all options (however weird the idea of creating a view inside a trigger might be...) you can create a view in a trigger. Yes, an implicit COMMIT will follow, but if we make the trigger work in autonomous transaction, then the dynamic DDL will not fail. Using Luke Woodward's example:
CREATE TABLE test (a integer);
INSERT INTO test (a) VALUES (5);
CREATE OR REPLACE TRIGGER test_trig
AFTER UPDATE ON test
FOR EACH ROW
DECLARE
-- making the trigger work outside of the main transaction
PRAGMA autonomous_transaction;
BEGIN
EXECUTE IMMEDIATE 'CREATE OR REPLACE VIEW test_view AS SELECT * FROM test';
END;
/
UPDATE test SET a = 6;
SELECT * FROM test_view;
A
----------
6
Check at SQLFiddle
No, you can't. Creating a view forces a commit, and you cannot commit in a trigger.
Here's what happens when you try to do this:
SQL> CREATE TABLE test (a integer);
Table created.
SQL> INSERT INTO test (a) VALUES (5);
1 row created.
SQL> COMMIT;
Commit complete.
SQL> CREATE OR REPLACE TRIGGER test_trig
2 AFTER UPDATE ON test
3 FOR EACH ROW
4 BEGIN
5 EXECUTE IMMEDIATE 'CREATE OR REPLACE VIEW test_view AS SELECT * FROM test';
6 END;
7 /
Trigger created.
SQL> UPDATE test SET a = 6;
UPDATE test SET a = 6
*
ERROR at line 1:
ORA-04092: cannot COMMIT in a trigger
ORA-06512: at "LUKE.TEST_TRIG", line 2
ORA-04088: error during execution of trigger 'LUKE.TEST_TRIG'
Related
I have a PL/SQL package that provides a transaction API for creating an instance of an entity (say a new customer). The API involves several DML steps.
There is a view that exposes instances of this (customer) entity and there is an INSTEAD OF trigger on the view that calls the transaction API whenever someone inserts into the view.
Normally, I would like my transaction API to not know or care about whether it is being called from a trigger. I want it to work like a typical API (typical around here, anyway):
Establish savepoint
Do steps 1-3 of DML
Do NOT commit (leave that to caller / client)
On others rollback to savepoint
The problem is that an API like this fails if called from a trigger.
I understand why Oracle cannot allow us to commit or rollback in trigger. But why does Oracle not allow us to rollback to savepoint that the trigger established?
How can I write my API so that:
It cannot have side-effects if any DML step fails halfway through
It's successful work is commited when the caller / client commits (i.e., autonomous transaction is a no-go)
It does not rely on the caller to raise_application_error if it fails. (Obviously, if I could rely on trigger callers to do this and on client code to then rollback, I won't need to worry about side-effects).
I'm a bit confused by your statement. PL/SQL programs automatically have a "natural" savepoint at their commencement, so that if it fails, it will rollback any changes it made, but leave existing changes in the current transaction untouched, eg
SQL> create table t ( x int );
Table created.
SQL>
SQL> create or replace
2 procedure my_api(p_fail boolean) is
3 v int;
4 begin
5 insert into t values (4);
6
7 insert into t values (5);
8
9 if p_fail then
10 v := 1/0;
11 end if;
12
13 insert into t values (6);
14 end;
15 /
Procedure created.
SQL>
SQL> insert into t values (1);
1 row created.
SQL> insert into t values (2);
1 row created.
SQL> insert into t values (3);
1 row created.
SQL> exec my_api(false);
PL/SQL procedure successfully completed.
SQL>
SQL> select * from t;
X
----------
1
2
3
4
5
6
6 rows selected.
API worked, so pre-API changes PLUS the API changes are all there. I'll roll back now to an empty state and let the API fail during execution
SQL>
SQL> rollback;
Rollback complete.
SQL> select * from t;
no rows selected
SQL>
SQL> insert into t values (1);
1 row created.
SQL> insert into t values (2);
1 row created.
SQL> insert into t values (3);
1 row created.
SQL> exec my_api(true);
BEGIN my_api(true); END;
*
ERROR at line 1:
ORA-01476: divisor is equal to zero
ORA-06512: at "MCDONAC.MY_API", line 9
ORA-06512: at line 1
SQL>
SQL> select * from t;
X
----------
1
2
3
My API inserts are rolled back but the pre-API inserts are still there.
I am using before update trigger for each row on table, say emp_table to update one column modifid_date before loading into table. If I am going to update the table with same/existing values of a row, then is this trigger going to fire or not?
condition in trigger:
:new.modifid_dt := sysdate;
Table Values before update: john (name),4867 (id),20-04-2016 (modifid_dt)
Table values now going to update: john (name),4867 (id)
Your trigger will be fired, no matter the values you are using; for example:
SQL> create table testTrigger ( a number)
2 /
Table created.
SQL> CREATE OR REPLACE TRIGGER before_update_trigger
2 before update on testTrigger
3 for each row
4 begin
5 dbms_output.put_line('Trigger fired!');
6 end;
7 /
Trigger created.
SQL> insert into testTrigger values (10);
1 row created.
SQL>
SQL>
SQL> update testTrigger set a = 10;
Trigger fired!
1 row updated.
SQL> update testTrigger set a = 11;
Trigger fired!
1 row updated.
SQL>
If you want avoid "false" firing you should write trigger like this:
create or replace trigger trigger1
before update on tst
for each row
begin
IF :new.t_key != :old.t_key AND ... THEN
dbms_output.put_line('Trigger fired!');
END IF;
end;
But beware of NULL values, of course.
New or existing values - no matter, anyway you'll perform an update so trigger will fire.
I'm wondering if I will miss any data if I replace a trigger while my oracle database is in use. I created a toy example and it seems like I won't, but one of my coworkers claims otherwise.
create table test_trigger (id number);
create table test_trigger_h (id number);
create sequence test_trigger_seq;
--/
create or replace trigger test_trigger_t after insert on test_trigger for each row
begin
insert into test_trigger_h (id) values (:new.id);
end;
/
--/
begin
for i in 1..100000 loop
insert into test_trigger (id) values (test_trigger_seq.nextval);
end loop;
end;
/
--/
begin
for i in 1..10000 loop
execute immediate 'create or replace trigger test_trigger_t after insert on test_trigger for each row begin insert into test_trigger_h (id) values (:new.id); end;';
end loop;
end;
/
ran the two loops at the same time
select count(1) from test_trigger;
COUNT(1)
100000
select count(1) from test_trigger_h;
COUNT(1)
100000
create or replace is locking the table. So all the inserts will wait until it completes. Don't worry about missed inserts.
I think you might be going about testing this in the wrong way. Your insert statements won't take any time at all and so the replacement of the trigger can fit in through the gaps between inserts. As least this is what I infer due to the below.
If you change your test to ensure you have a long running SQL statement, e.g.
create table test_trigger (id number);
create table test_trigger_h (id number);
create sequence test_trigger_seq;
create or replace trigger test_trigger_t
after insert on test_trigger for each row
begin
insert into test_trigger_h (id) values (:new.id);
end;
/
insert into test_trigger
select level
from dual
connect by level <= 1000000;
If you then try to replace the trigger in a separate session it will not occur until after the insert has completed.
Unfortunately, I can't find anything in the documentation to back me up; this is just behavior that I'm aware of.
Following URL answers that trigger can be modified while application is running. its will a "library cache" lock and NOT a "data" lock. Oracle handles it internally without you worrying abt it.
Check out question raised by Ben- Can a trigger be locked; how would one determine that it is?
-- Run this from session 2:
select * from v$access where object = upper('test_trigger_t');
Simple one. I´m a bit of a newvbie with PLSql and oracle's error messages are never too helpful.
I want to do a simple trigger to update a column with the current date i.e. 'modified date' column of a table. Getting an odd error though.
The idea is simple
create table test1 (tcol varchar2(255), tcol2 varchar2(255))
CREATE OR REPLACE TRIGGER testTRG
AFTER INSERT OR UPDATE ON test1
FOR EACH ROW
BEGIN
update test1
set tcol2 = to_char(sysdate)
where tcol = :OLD.tcol;
END;
insert into test1 (tcol) values ('test1');
this pops up the error:
ORA-04091: table RAIDBIDAT_OWN.TEST1 is mutating, trigger/function may not see it
ORA-06512: at "RAIDBIDAT_OWN.TESTTRG", line 2
ORA-04088: error during execution of trigger 'RAIDBIDAT_OWN.TESTTRG'
Would anyone have a quick solution for this?
cheers,
f.
Your situation:
SQL> create table test1 (tcol varchar2(255), tcol2 varchar2(255))
2 /
Table created.
SQL> CREATE OR REPLACE TRIGGER testTRG
2 AFTER INSERT OR UPDATE ON test1
3 FOR EACH ROW
4 BEGIN
5 -- Your original trigger
6 update test1
7 set tcol2 = to_char(sysdate)
8 where tcol = :OLD.tcol;
9 END;
10 /
Trigger created.
SQL> insert into test1 (tcol) values ('test1');
insert into test1 (tcol) values ('test1')
*
ERROR at line 1:
ORA-04091: table [schema].TEST1 is mutating, trigger/function may not see it
ORA-06512: at "[schema].TESTTRG", line 3
ORA-04088: error during execution of trigger '[schema].TESTTRG'
Tony's suggestion is almost right, but unfortunately it doesn't compile:
SQL> CREATE OR REPLACE TRIGGER testTRG
2 AFTER INSERT OR UPDATE ON test1
3 FOR EACH ROW
4 BEGIN
5 -- Tony's suggestion
6 :new.tcol2 := sysdate;
7 END;
8 /
CREATE OR REPLACE TRIGGER testTRG
*
ERROR at line 1:
ORA-04084: cannot change NEW values for this trigger type
Because you can only change NEW values in before-each-row triggers:
SQL> create or replace trigger testtrg
2 before insert or update on test1
3 for each row
4 begin
5 :new.tcol2 := sysdate;
6 end;
7 /
Trigger created.
SQL> insert into test1 (tcol) values ('test1');
1 row created.
SQL> select * from test1
2 /
TCOL
------------------------------------------------------------------------------------------
TCOL2
------------------------------------------------------------------------------------------
test1
13-09-2010 12:37:24
1 row selected.
Regards,
Rob.
The trigger should simply read:
CREATE OR REPLACE TRIGGER testTRG
BEFORE INSERT OR UPDATE ON test1
FOR EACH ROW
BEGIN
:new.tcol2 := to_char(sysdate);
END;
There is no requirement to issue another update of the same row (and as you have found, you cannot).
It is more usual to use DATE columns to store dates:
create table test1 (tcol varchar2(255), tcol2 date);
CREATE OR REPLACE TRIGGER testTRG
BEFORE INSERT OR UPDATE ON test1
FOR EACH ROW
BEGIN
:new.tcol2 := sysdate;
END;
I want to create a trigger that execute on update of a table.
in particular on update of a table i want to update another table via a trigger but if the trigger fails (REFERENTIAL INTEGRITY-- ENTITY INTEGRITY) i do not want to execute the update anymore.
Any suggestion on how to perform this?
Is it better to use a trigger or do it anagrammatically via a stored procedure?
Thanks
The DML in the trigger is part of the same action as the triggering DML. Both have to succeed or b oth fail. If the trigger raises an unhandled exception the entire statement gets rolled back.
Here is a trigger on T23 which copies the row into T42.
SQL> create or replace trigger t23_trg
2 before insert or update on t23 for each row
3 begin
4 insert into t42 values (:new.id, :new.col1);
5 end;
6 /
Trigger created.
SQL>
A successful inserrt into T23...
SQL> insert into t23 values (1, 'ABC')
2 /
1 row created.
SQL> select * from t42
2 /
ID COL
---------- ---
1 ABC
SQL>
But this one will fail because of a unique constraint on T42.ID. As you can see the triggering statement is rolled back too ...
SQL> insert into t23 values (1, 'XYZ')
2 /
insert into t23 values (1, 'XYZ')
*
ERROR at line 1:
ORA-00001: unique constraint (APC.T24_PK) violated
ORA-06512: at "APC.T23_TRG", line 2
ORA-04088: error during execution of trigger 'APC.T23_TRG'
SQL> select * from t42
2 /
ID COL
---------- ---
1 ABC
SQL> select * from t23
2 /
ID COL
---------- ---
1 ABC
SQL>
If the trigger fails, it will raise an exception ( unless you specifically tell it not to ), in which case, you would have the client rollback. It doesn't really matter if its done via a trigger or a SP ( although its often a good idea to keep a logical transaction within a SP, rather than spread it around triggers ).