My trigger didnt work he have a problem with the When - oracle

I'm trying to create a trigger that automatically adds an end of reign date to the govern table if the datedeath column of the character table is updated. but it doesn't work
CREATE TRIGGER maj_date_gouv
AFTER UPDATE OF Datedeath ON Personnage
WHEN (new.Datedeath <> NULL)
FOR EACH ROW
BEGIN
IF UPDATING THEN
UPDATE Govern SET Dateendgov = Datedeath
WHERE IdPersonnage = :new.IdPersonnage
END IF ;
END ;
ORA-04077: WHEN clause cannot be used with table level triggers
*Cause: The when clause can only be specified for row level triggers.
*Action: Remove the when clause or specify for each row.

Order matters:
for each row first
when next
if you compare something to null, then it is either is null or is not null (not "<> null")
So:
SQL> create or replace trigger maj_date_gouv
2 after update of datedeath on personnage
3 for each row
4 when (new.datedeath is not null)
5 begin
6 if updating then
7 update govern set
8 dateendgov = datedeath
9 where idpersonnage = :new.idpersonnage;
10 end if;
11 end;
12 /
Trigger created.
SQL>

Related

PLSQL Trigger - Update another table before inserting a new record

I have 3 tables that are related to each other:
ACCOUNTS
CARDS
TRANSACTIONS
I want to change the money amount from account every time I execute a new transaction. I want to decrease the account value with each new move.
I tried writing this trigger:
create or replace trigger ceva_trig1
before insert on miscari
for each row
declare
new_val micari.valoare%tipe := new.valoare;
begin
update conturi
set sold = sold - new_val
where nrcont = (select nrcont
from conturi
join carti_de_credit on conturi.nrcont = carti_de_credit.nrcont
join miscari on carti_de_credit.nr_card = miscari.nrcard)
and sold >= new_val;
end;
May anyone help me correct the syntax that crashes here?
I've created those tables with minimal number of columns, just to make trigger compile.
SQL> create table conturi
2 (sold number,
3 nrcont number
4 );
Table created.
SQL> create table miscari
2 (valoare number,
3 nrcard number
4 );
Table created.
SQL> create table carti_de_credit
2 (nrcont number,
3 nr_card number
4 );
Table created.
Trigger:
SQL> create or replace trigger ceva_trig1
2 before insert on miscari
3 for each row
4 begin
5 update conturi c
6 set c.sold = c.sold - :new.valoare
7 where c.nrcont = (select r.nrcont
8 from carti_de_credit r
9 where r.nrcont = c.nrcont
10 and r.nr_card = :new.nrcard
11 )
12 and c.sold >= :new.valoare;
13 end;
14 /
Trigger created.
SQL>
How does it differ from your code? Like this:
SQL> create or replace trigger ceva_trig1
2 before insert on miscari
3 for each row
4 declare
5 new_val micari.valoare%tipe := new.valoare;
6 begin
7 update conturi
8 set sold = sold - new_val
9 where nrcont = (select nrcont
10 from conturi
11 join carti_de_credit on conturi.nrcont = carti_de_credit.nrcont
12 join miscari on carti_de_credit.nr_card = miscari.nrcard)
13 and sold >= new_val;
14 end;
15 /
Warning: Trigger created with compilation errors.
SQL> show err
Errors for TRIGGER CEVA_TRIG1:
LINE/COL ERROR
-------- -----------------------------------------------------------------
2/11 PL/SQL: Item ignored
2/26 PLS-00208: identifier 'TIPE' is not a legal cursor attribute
4/3 PL/SQL: SQL Statement ignored
10/15 PL/SQL: ORA-00904: "NEW_VAL": invalid identifier
10/15 PLS-00320: the declaration of the type of this expression is incomplete or malformed
SQL>
Explained:
it isn't tipe but type
new column values are referenced with a colon, i.e. :new.valoare
you shouldn't make typos regarding table & column names; it is miscari, not micari
it is bad practice to write query which references the same table (miscari, line #12) trigger is created for. As it is being changed, you can't select values from it as it is mutating
lucky you, you don't have to do that at all. How? Have a look at my code.
Attempting to maintain an ongoing for transactions in one table in another table is always a bad idea. Admittedly in an extremely few cases it's necessary, but should be the design of last resort not an initial one; even when necessary it's still a bad idea and therefore requires much more processing and complexity.
In this instance after you correct all the errors #Littlefoot points out then your real problems begin. What do you do when: (Using Littlefoot's table definitions)
I delete a row from miscari?
I update a row in miscari?
The subselect for nrcont returns 0 rows?
The condition sold >= new_val is False?
If any of conditions occur the value for sold in conturi is incorrect and may not be correctable from values in the source table - miscari. An that list may be just the beginning of the issues you face.
Suggestion: Abandon the idea of keeping an running account of transaction values. Instead derive it when needed. You can create a view that does that and select from the view.
So maybe instead of "create table conturi ..."

ORA-04091 (mutating table) only if specific field is filled

Got a table, which has a key on itself:
+----+-----------+-----------+
+ ID + ID_PARENT + IS_PARENT +
+----+-----------+-----------+
+ 1 + (null) + 0 +
+ 2 + (null) + 1 +
+ 3 + 2 + 0 +
+----+-----------+-----------+
As you see, ID 1 is on its own, 3 is a child of 2.
Now I want to have a trigger, that on INSERT/UPDATE ...
errors, if an inserted row is it's own parent (not possible)
if it has an ID_PARENT, set the parent's IS_PARENT to 1
This is my approach:
CREATE OR REPLACE TRIGGER tri_table_set_parent
BEFORE
INSERT OR UPDATE ON table
FOR EACH ROW
WHEN ( new.id_parent IS NOT NULL )
BEGIN
IF :new.id = :new.id_parent
THEN
RAISE_APPLICATION_ERROR(-20666, 'A gap cant be the parent of itself. More information here: https://youtu.be/hqRZFWE1X_A');
END IF;
UPDATE table
SET is_parent = 1
WHERE id = :new.id_parent;
END;
/
The error works as intended, woohoo! But now when inserting, I have problems.
When inserting a row without ID_PARENT, it works (because trigger won't trigger at all).
Inserting a row, whose parent's ID_PARENT = (null):
INSERT INTO table (ID, ID_PARENT) VALUES (4, 1);
-> Works!
But inserting a row, whose parent got an ID_PARENT:
INSERT INTO table (ID, ID_PARENT) VALUES (5, 3);
-> Errors:
ORA-04091: Tabelle TABLE wird gerade geändert, Trigger/Funktion sieht dies möglicherweise nicht
ORA-06512: in "TRI_TABLE_SET_PARENT", Zeile 6
ORA-04088: Fehler bei der Ausführung von Trigger "TRI_TABLE_SET_PARENT"
ORA-06512: in "TRI_TABLE_SET_PARENT", Zeile 6
ORA-04088: Fehler bei der Ausführung von Trigger "TRI_TABLE_SET_PARENT"
Updating the table doesn't work at all, same error.
Ok, so I understand that I can't select stuff that might be changed simultaniously. But I'm updating, and also I'm checking that I don't reference the same rows.
So what am I missing?
The evil thing in relational database is redundancy which you try to introduce.
More correct relational approach would be to define the table without the IS_PARENT column.
select * from my_parent order by id;
ID ID_PARENT
---------- ----------
1
2
3 2
... and add the redundant column in an access view
create view V_MY_PARENT as
select a.ID, a.ID_PARENT,
case when exists (select null from my_parent where ID_PARENT = a.ID) then 1 else 0 end as IS_PARENT
from my_parent a
order by a.ID;
To get all non parents you access the view
select * from V_MY_PARENT
where is_parent = 0;
ID ID_PARENT IS_PARENT
---------- ---------- ----------
1 0
3 2 0
If you want to materialize the redundancy (e.g. for performance reasons) use MATERIALIZED VIEWs.
With this approach you will not end with parents classified as no parents or vice versa, which is quite possible in your design.
The error cause is that you are updating row that you didn't inserted yet.
you could update the data you are inserting using :new keyword.
so the solution will be replacing the update statement with
:new.is_parent := 1
ORA-04091 (table is mutating, trigger may not see it) occurs here because you have a BEFORE trigger defined on "TABLE", and in the body of the trigger you're attempting to update "TABLE". Oracle doesn't allow this as it can lead to a trigger loop (i.e. if this was allowed your program could execute a statement which causes the trigger to fire; within the body of the trigger a statement executes which causes the trigger to fire; within the body of the trigger a statement executes which causes the trigger to fire; within the body of the trigger a statement executes which causes the trigger to fire; within the body of the trigger a statement executes which causes the trigger to fire; etc). So you're not allowed to do this in a BEFORE trigger. The simplest fix is to change the trigger to an AFTER trigger. In other words, change BEFORE INSERT OR UPDATE ON table to AFTER INSERT OR UPDATE ON table. In this particular case it doesn't appear this would be a problem, but I don't know what constraints you have on your table which might make this unacceptable. Give it a try.
Just Replace
UPDATE "table"
SET id_parent = 1
WHERE id = :new.id_parent;
with
if :old.id = :new.id_parent and updating then
:new.id_parent := 1;
end if;
e.g avoid using a DML within the same "table" against ORA-04091.
P.S. being table as a reserved keyword, I replaced with "table".

create trigger to save CONSTRAINTs changes on tables in oracle

I want to create table to save all CONSTRAINTs changes in my oracle database,
so i have created this table(table name , constraint name , date , mode like [insert|update|delete] )
CREATE TABLE CONS
(
C_ID NUMBER NOT NULL
, C_NAME VARCHAR2(50) NOT NULL
, T_NAME VARCHAR2(50) NOT NULL
, EXE_DATE DATE NOT NULL
, MODE VARCHAR2(50) NOT NULL
);
the problem was by insert data,
I was thinking of creating trigger on user_cons_columns after insert or update or delete,
but I found that user_cons_columns is a view and i can't create trigger on it,
so how can I do this work?
or what is the tables that I can create trigger on it to do this???
thanks .......
User_Cons_Columns is metadata view in oracle and you can not use it for monitoring constraint changes.
I think, You can use this metadata view :
SELECT *
FROM ALL_CONSTRAINTS T
It shows which constraint has changed by LAST_CHANGE Column and other data that you can use them.
I don't know if we can create DML TRIGGERs on data dictionary tables in Oracle, but that sounds like a bad idea.
My suggestion to you would be to create a DDL Trigger on the ALTER event
Firstly, as a one time activity, you could store all the available constraints in your CONS table.
Then in your trigger, you could use these conditions to check if a table and column was altered.
if (ora_sysevent = 'ALTER' and
ora_dict_obj_type = 'TABLE')
then alter_column :=
ora_is_alter_column('FOO');
end if;
You could then query the user_cons_columns , ALL_CONSTRAINTS and CONS - whichever you find is relevant to store your data - to find if a new constraint was added or not. If it was indeed added or modified, make an entry into CONS or else update it ( using MERGE statement )
As you said, you can't create that type of a trigger on USER_CONS_COLUMNS:
SQL> create or replace trigger trg_myucc
2 before insert
3 on user_cons_columns
4 begin
5 null;
6 end;
7 /
create or replace trigger trg_myucc
*
ERROR at line 1:
ORA-25001: cannot create this trigger type on this type of view
Why wouldn't we try INSTEAD OF TRIGGER?
SQL> create or replace trigger trg_myucc
2 instead of insert
3 on user_cons_columns
4 for each row
5 begin
6 null;
7 end;
8 /
on user_cons_columns
*
ERROR at line 3:
ORA-01031: insufficient privileges
OK, that won't work either. But, what prevents us on creating a view upon a view, and then create INSTEAD OF trigger on that newly created view?
SQL> create or replace view my_ucc as select * from user_cons_columns;
View created.
SQL> create or replace trigger trg_myucc
2 instead of insert
3 on my_ucc
4 for each row
5 begin
6 null;
7 end;
8 /
Trigger created.
Fine; now you have a way to do what you're supposed to do. More about triggers here.

Table is mutating, trigger/function may not see it ORA-06512 [duplicate]

This question already has answers here:
Oracle trigger after insert or delete
(3 answers)
Closed 7 years ago.
I have two tables called DetailRental and Video. VID_NUM is the PK of Video and the FK of DetailRental.
What this code wants to achieve is when the Detail_Returndate or Detail_Duedate from DetailRental table changes(update or insert new row), the trigger will check the value of Detail_Returndate row by row. If its value is null, then the corresponding(according to VID_NUM) attribute VID_STATUS from Video table will change to "OUT".
The trigger has been created successfully. However, when I want to update the date. Oracle gives me error:
ORA-04091: table SYSTEM2.DETAILRENTAL is mutating, trigger/function may not see it
ORA-06512: at "SYSTEM2.TRG_VIDEORENTAL_UP", line 3
ORA-04088: error during execution of trigger 'SYSTEM2.TRG_VIDEORENTAL_UP'
1. UPDATE DETAILRENTAL
2. SET DETAIL_RETURNDATE = null
3. WHERE RENT_NUM = 1006 AND VID_NUM = 61367
Below is my code:
CREATE OR REPLACE TRIGGER trg_videorental_up
AFTER INSERT OR UPDATE OF DETAIL_RETURNDATE, DETAIL_DUEDATE ON DETAILRENTAL
FOR EACH ROW
AS
DECLARE
DTRD DATE;
BEGIN
SELECT DETAIL_RETURNDATE
INTO DTRD
FROM DETAILRENTAL;
IF DTRD IS NULL
THEN UPDATE VIDEO
SET VIDEO.VID_STATUS = 'OUT'
WHERE EXISTS
(SELECT DETAILRENTAL.VID_NUM
FROM DETAILRENTAL
WHERE DETAILRENTAL.VID_NUM = VIDEO.VID_NUM
);
END IF;
END;
Thank you very much!
problem solved here is the code:
CREATE OR REPLACE TRIGGER trg_videorental_up
AFTER INSERT OR UPDATE OF DETAIL_RETURNDATE, DETAIL_DUEDATE ON DETAILRENTAL
FOR EACH ROW
DECLARE DETAIL_RETURNDATE DATE;
BEGIN
IF :NEW.DETAIL_RETURNDATE IS NULL THEN UPDATE VIDEO SET VID_STATUS = 'OUT' WHERE VID_NUM = :NEW.VID_NUM;
ELSIF :NEW.DETAIL_RETURNDATE > SYSDATE THEN UPDATE VIDEO SET VID_STATUS = 'OUT' WHERE VID_NUM = :NEW.VID_NUM;
ELSIF :NEW.DETAIL_RETURNDATE <= SYSDATE AND TO_CHAR(DETAIL_RETURNDATE)!= '01/01/0001' THEN UPDATE VIDEO SET VID_STATUS = 'IN' WHERE VID_NUM = :NEW.VID_NUM;
ELSIF :NEW.DETAIL_RETURNDATE = '01/01/0001' THEN UPDATE VIDEO SET VID_STATUS = 'LOST' WHERE VID_NUM = :NEW.VID_NUM;
END IF;
END;
A good data model is one in which no redundant information is physically stored. If you can look at one (or more) values in a table.column and figure out what value should be in another table.column, then you've got a redundancy. In your case, a person can see a DETAILRENTAL.DETAIL_DUEDATE for VIDNUM 61367 is not null, and "know" that the VIDEO.STATUS field should be OUT.
Most easily fixed with something like:
1) Create a VIDEO_BASE table, with all VIDEO columns except VID_STATUS:
CREATE TABLE VIDEO_BASE AS
SELECT {list all columns except STATUS}
FROM VIDEO;
2) Drop original VIDEO table and create as a view, VIDEO, which shows all columns of VIDEO_BASE, plus exposes STATUS as a derived field:
CREATE OR REPLACE VIEW VIDEO
AS
SELECT V.*,
CASE WHEN
(
SELECT COUNT(*)
FROM
(
SELECT 'X'
FROM DETAILRENTAL D
WHERE D.VID_NUM = V.VID_NUM
AND DETAIL_RETURNDATE IS NOT NULL
AND ROWNUM <= 1
)
) > 0
THEN 'OUT'
ELSE NULL
END VID_STATUS
FROM VIDEO_BASE V;
In general, if you feel you need a trigger to keep two different tables in sync, you've got a data model problem. In my 15+ years experience with Oracle, the only best way to fix problematic triggers is to fix the data model - the surest way to know that all your triggers are working properly is when the number of triggers in your database is 0.
After reading through #KevinKirkpatrick's answer two or three times, I realise he's right - an individual video's in/out status is derivable from other information in the database. That said, you may have pragmatic reasons for doing it this way.
The bad news is that you can't select from a table within a row trigger on that same table - that's what the "mutating table" problem means. The good news is that in this case you don't really need to.
I don't have an Oracle installation I can test this on, so I make no guarantee of syntactic correctness, but it should be close enough to get you started.
CREATE OR REPLACE TRIGGER trg_videorental_up
AFTER INSERT OR UPDATE
OF detail_duedate, detail_returndate
ON detailrental
FOR EACH ROW
AS
BEGIN
IF :new.detail_returndate IS NULL
AND :new.detail_duedate IS NOT NULL
THEN
UPDATE video
SET status = 'OUT'
WHERE video_num = :new.video_num;
END IF;
END;

Trigger in oracle. Update field when insert or update another field

I need To update the field DATAMARKER"of my table LOG_ALARMA when I have one INSTER or UPDATE of "CONTADOR".
i have this, but return muting error.
create or replace TRIGGER TRIGGER2
AFTER INSERT OR UPDATE OF CONTADOR ON LOG_ALARMA
for each row
BEGIN
UPDATE LOG_ALARMA a
SET a.DATAMARKER=(SYSDATE);
END;
I look another examples and they work but i can't execute this correctly.
IF i comment the line for each row in my trigger body then it is working fine but it UPDATES all the rows in my table.
You do not issue an update SQL statement, because that would again cause the trigger to fire.
Instead, you just set the value :new.DATAMARKER to sysdate, using PL/SQL not SQL.
Make it a BEFORE INSERT OR UPDATE also.
CREATE TABLE Mutating
(
ID1 NUMBER,
DATE1 DATE
)
data present in a table is
ID1 DATE1
1 09/01/2015 14:09:14
1 08/31/2015 14:09:21
2 08/30/2015 14:09:30
Now i want to update the date1 if any update happens on id1 column in that situation i have used trigger look below.
CREATE OR REPLACE TRIGGER Mutating_trg
before INSERT OR DELETE OR UPDATE ON Mutating
referencing old as old new as new
for each row
begin
if updating then
:new.date1:=sysdate;
end if;
end;
then i have issued update statement
update set Mutating id1=6 where trunc(date1)=trunc(sysdate-2)
1 row updated
Now look into results
ID1 DATE1
1 09/01/2015 14:09:14
6 09/02/2015 14:09:14
2 08/30/2015 14:09:30
You should use before statement.

Resources