Oracle after update trigger creating public database link - oracle

I have an error: 'ORA-04092: cannot COMMIT in a trigger' when trying to execute ddl command in one simple oracle after update trigger. Trigger needs to create public database link after one field in column is updated. Here is the source:
create or replace
TRIGGER CreateLinkTrigger
after UPDATE of Year ON tableInit
for each row
DECLARE
add_link VARCHAR2(200);
BEGIN
IF :new.year = '2014'
then
add_link := q'{create public database link p2014 connect to test14 identified by temp using 'ora'}';
execute immediate add_link;
END IF;
END;
So, as You can see i need to create new public database link after new year has been activated.
So when i try to update table 'tableInit' with year value of '2014' i get ORA-04092 error.
Is there any way to avoid this error, or another solution for this?
Thanks...

Creating a database link on the fly seems like an unusual thing to do; your schema should generally be static and stable. However, if you must, it would be simpler to wrap the update and the link in a procedure, or just issue two statements - presumably whatever performs the update is fairly controlled anyway, otherwise you'd have to deal with multiple people triggering this multiple times, which would be even more of a mess.
You can probably make this work by adding PRAGMA autonomous_transaction; to your trigger, as demonstrated for a similar issue (creating a view rather than a link) in this answer, but I'm not in a position to test that at the moment.
create or replace
TRIGGER CreateLinkTrigger
after UPDATE of Year ON tableInit
for each row
DECLARE
add_link VARCHAR2(200);
PRAGMA autonomous_transaction;
BEGIN
...
You could also make the trigger submit an asynchronous job to perform the DDL, as described in this answer, and there's more of an example in this answer, where you'd change the job's anonymous block to do your execute immediate.
It would probably be better to just create the links for the next few years in advance during a maintenance window, or on a schedule, or from a procedure; rather than trying to associate a schema change to a data change.

Related

Mutating Trigger Error with Trigger in Oracle PL/SQL [duplicate]

I get an error (ORA-04091: table DBPROJEKT_AKTIENDEPOT.AKTIE is mutating, trigger/function may not see it) when executing my trigger:
CREATE OR REPLACE TRIGGER Aktien_Bilanz_Berechnung
AFTER
INSERT OR UPDATE OF TAGESKURS
OR INSERT OR UPDATE OF WERT_BEIM_EINKAUF
ON AKTIE
FOR EACH ROW
DECLARE
bfr number;
Begin
bfr := :new.TAGESKURS - :new.WERT_BEIM_EINKAUF;
UPDATE AKTIE
SET BILANZ = TAGESKURS - WERT_BEIM_EINKAUF;
IF bfr < -50
THEN
DBMS_OUTPUT.PUT_LINE('ACHTUNG: The value (Nr: '||:new.AKTIEN_NR||') is very low!');
END IF;
END;
I want to check the value "BILANZ" after calculating it, wether it is under -50.
Do you have any idea why this error is thrown?
Thanks for any help!
There are several issues here:
Oracle does not allow you to perform a SELECT/INSERT/UPDATE/DELETE against a table within a row trigger defined on that table or any code called from such a trigger, which is why an error occurred at run time. There are ways to work around this - for example, you can read my answers to this question and this question - but in general you will have to avoid accessing the table on which a row trigger is defined from within the trigger.
The calculation which is being performed in this trigger is what is referred to as business logic and should not be performed in a trigger. Putting logic such as this in a trigger, no matter how convenient it may seem to be, will end up being very confusing to anyone who has to maintain this code because the value of BILANZ is changed where someone who is reading the application code's INSERT or UPDATE statement can't see it. This calculation should be performed in the INSERT or UPDATE statement, not in a trigger. It considered good practice to define a procedure to perform INSERT/UPDATE/DELETE operations on a table so that all such calculations can be captured in one place, instead of being spread out throughout your code base.
Within a BEFORE ROW trigger you can modify the values of the fields in the :NEW row variable to change values before they're written to the database. There are times that this is acceptable, such as when setting columns which track when and by whom a row was last changed, but in general it's considered a bad idea.
Best of luck.
You are modifying the table with the trigger. Use a before update trigger:
CREATE OR REPLACE TRIGGER Aktien_Bilanz_Berechnung
BEFORE INSERT OR UPDATE OF TAGESKURS OR INSERT OR UPDATE OF WERT_BEIM_EINKAUF
ON AKTIE
FOR EACH ROW
DECLARE
v_bfr number;
BEGIN
v_bfr := :new.TAGESKURS - :new.WERT_BEIM_EINKAUF;
:new.BILANZ := v_bfr;
IF v_bfr < -50 THEN
Raise_Application_Error(-20456,'ACHTUNG: The value (Nr: '|| :new.AKTIEN_NR || ') is very low!');
END IF;
END;

How to update a trigger in Oracle

I created a trigger like that :
create or replace
TRIGGER "TRIG_DECLENCHEMENT_PARAM"
AFTER UPDATE ON t_balise
FOR EACH ROW
WHEN (NEW.no_serie like '2%')
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
P_UPDATE_BALISE(:NEW.no_serie, :NEW.date, :NEW.vitesse);
COMMIT;
END;
P_UPDATE_BALISE it's a method in another data base which update another table. It works well like that. I want to update this Trigger and change the condition NEW.no_serie like '2%' to NEW.no_serie between 200 and 299. There is a script like Alter Trigger... which make an update of Trigger ?
The alter trigger statement only lets you "enable, disable, or compile a database trigger". The documentation states:
Note:
This statement does not change the declaration or definition of an existing trigger. To redeclare or redefine a trigger, use the CREATE TRIGGER statement with the OR REPLACE keywords.
You will have to modify your existing statement (which should probably be in source control anyway) to have the new condition:
create or replace
TRIGGER "TRIG_DECLENCHEMENT_PARAM"
AFTER UPDATE ON t_balise
FOR EACH ROW
WHEN (NEW.no_serie between 200 and 299)
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
P_UPDATE_BALISE(:NEW.no_serie, :NEW.date, :NEW.vitesse);
COMMIT;
END;
You've changed that comparison from a string to a number. Hopefully no_serie is actually a number and it was the old check that was incorrectly treating it as a string, and triggering on values you didn't want (2, 20, 2000 etc.).
Not directly relevant, but having this trigger as an autonomous transaction means that if the update on t_balise is rolled back, any changes made by the call to P_UPDATE_BALISE will not be rolled back - since they've been committed independently. That isn't generally something you want - the update on this table and whatever changes are made elsewhere (you said to another table; hopefully you haven't made this autonomous because the procedure is actually updating the same table) would normally be atomic and part of the same transaction. Breaking atomicity is something that is very rarely needed or desirable, so I'd check that is really what you intended and isn't just a hack to avoid a deeper problem.

How to update a table using an AFTER INSERT trigger in that same table?

I have 2 triggers on a table A one is BEFORE INSERT and one AFTER INSERT :
CREATE OR REPLACE TRIGGER DEMO_TRG
AFTER INSERT
ON A
REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
declare
PRAGMA AUTONOMOUS_TRANSACTION;
begin
UPDATE A
SET STATUS = 'DONE'
WHERE id_id =:new.p_id;
commit;
END;
/
Why the update command in the after insert doesn't work (the status is not set to DONE)? Did I missed something?
Most likely the issue is due to the AUTONOMOUS_TRANSACTION - when you do that, you're effectively switching to a different transaction, which won't know about your uncommitted transaction (ie. your insert), so there is nothing for it to update.
What you should do is amend the column (:new.status := 'DONE') in your before trigger. Or, even better, avoid triggers and have the logic in a stored procedure and don't allow anyone to directly insert into the table, although I appreciate this may be a big switch if you have lots of applications inserting directly into tables etc

Trigger to create policy on Oracle

I have two schemas:
my_user sec_admin
sec_admin has the right the create policies on any table of my_user.
We have created a procedure inside of a package my_package.my_proc() that generates and executes all the create policy statements.
So far so good, everything has been working fine.
Now, we've realized that when my_user drops a table (and recreates it), the attached policy automatically gets dropped. A solution is to re-use manually my_package.my_proc() to re-generate the policy. But the fact is, the drop and create table statements are created by SAS developers who don't have access to Oracle SQL Developer. Therefore, the call to my_package.my_proc() should be automatized, by using a trigger
so we've created a simple trigger on my_user:
create or replace
TRIGGER test_trig
AFTER CREATE
ON SCHEMA
DECLARE
--oper ddl_log.operation%TYPE;
v_object_type varchar2(100);
BEGIN
SELECT ora_dict_obj_type
into v_object_type
FROM DUAL;
If upper(v_object_type)='TABLE' then
SEC_ADMIN.my_package.my_proc();
end if;
END test_trig;
It seems that when we create a table, the first time the trigger partially works. When we create a second table, the trigger seems to work, but PF_OWNER inside of dba_policies is my_user instead of SEC_ADMIN. Therefore my policy is not working anymore.
It seems that I have to run the procedure SEC_ADMIN.my_package.my_proc() as SEC_ADMIN as part of a solution but I don't know how.
I'm also open to alternative solutions if you have any. It seems like the problem is that whenever a table is dropped, the attached policy is dropped with it...
Thanks for your help

Keep a record of all deletes with a trigger, including rollbacks

Let's say I have 2 tables, EMPLOYEE and EMP_BAK. I need to create a backup table for all the data that deleted from employee, even these that are rolled back
My trigger:
CREATE OR REPLACE TRIGGER emp_del_bak_trg
before delete ON employee
FOR EACH row
DECLARE
oldname department.department_name%type;
newname department.department_name%type;
BEGIN
INSERT INTO emp_bak
VALUES (:OLD.employee_id, :OLD.employee_name, :OLD.job
,:OLD.hire_date,:OLD.department_id, sysdate);
--commit;
end;
Now, if I rollback then the data will be deleted; if I uncomment out commit, I'll get an error on deleting. The idea is to keep the record plus keep track of the system updates.
Any ideas how to get around this?
There is almost never a good reason to commit inside a trigger.
Now, that's out of the way your situation seems to be extra special, you need to track it, even if the transaction is rolled back. This is a fairly unusual requirement, but I'm going to assume you have a very good reason for doing this.
If you really, really, want to do this you need to use an autonomous transaction, this enables an independent transaction to be committed within another. As a trigger is a PL/SQL block, you can do this in a trigger.
The Oracle documentation has a separate section dealing with autonomous transactions in triggers, with plenty of examples for you. Wherever you need to use one the syntax is as follows, this is always placed in the DECLARE block:
PRAGMA AUTONOMOUS_TRANSACTION;
Your trigger, therefore, would look as follows:
create or replace trigger emp_del_bak_trg
before delete on employee
for each row
declare
PRAGMA AUTONOMOUS_TRANSACTION;
begin
insert into emp_bak
values ( :old.employee_id, :old.employee_name, :old.job
, :old.hire_date, :old.department_id, sysdate);
commit;
end;
/
As a little note I always case this differently so it's obvious you're doing something that you shouldn't be. Autonomous transactions are dangerous and should be used with extreme care.

Resources