I want to create a trigger that manages 2 magazines, 'Elle' and 'Time'.
So if the user tries to insert a new record in the magazine table that does not match these two, the latter should not be retained.
I already have all the tables and stuff ready there's no problem in that.
The problem is that I can't figure out a way to do this properly, a friend said that this code of mine interacts with lines only, and that I need a code for the whole table and recommended that I use a Cursor.
Here's my code:
Create or replace trigger TMag
After INSERT on Magazine
FOR EACH ROW
DECLARE
e EXCEPTION;
BEGIN
IF :new.mag_nom!= 'Elle' or :new.mag_nom!= 'Time' THEN
Delete from Magazine where ISBN=:new.ISBN;
raise e;
END IF;
exception
when e then dbms_output.put_line('nom mag incorrecte');
END;
Here's a look on my tables:
CLIENT(CIN, CL_NOM, CL_ADDR, CL_VILLE, EMAIL, CONTACT_NUM);
MAGAZINE(ISBN, MAG_NOM, PRIX_Mois);
ABONNEMENT(AB_ID, #ISBN, #IN_ID, Months);
INVOICE(IN_ID, #CIN, dateI, State) ;
Thanks in advance
I think you don't need a trigger, rather resolve the problem internally by creating a CHECK Constraint such as
ALTER TABLE Magazine
ADD CONSTRAINT correcte_mag_nom
CHECK (mag_nom IN ('Elle', 'Time'));
If the provided value is not eligible for mag_nom column, then it will hurl as
ORA-02290 check constrain (<schema>.CORRECTE_MAG_NUM) violated
You cannot use DML (your delete) on the same table that the trigger belongs to. Change your trigger to fire before the insert, and just raise the error to prevent the insert from happening in the first place.
Note that you must also declare a code number for the exception, and your logic should be if new.name!='Elle' AND new.name!='Time'. If you use "OR" then your trigger will not allow any inserts on the table at all...
CREATE OR REPLACE TRIGGER TMAG
BEFORE INSERT ON MAGAZINE
FOR EACH ROW
DECLARE
e_bad_mag_nom EXCEPTION;
PRAGMA EXCEPTION_INIT (e_bad_mag_nom, -20001)
BEGIN
IF :new.mag_nom!= 'Elle' AND :new.mag_nom!= 'Time' THEN
raise e_bad_mag_nom;
END IF;
EXCEPTION
when e_bad_mag_nom then dbms_output.put_line('nom mag incorrecte');
END;
See here for references:
https://www.oracletutorial.com/plsql-tutorial/plsql-raise/
How to raise an Exception inside a Trigger? Is there a way to do this?
Related
my trigger doesn't work, when I try to update a query. Any ideas why?
I feel like it is related to the WHERE conditions when I try to set variables.
I tried to do it without the WHERE conditions, but it still didn't work.
Any ideas why? Thank You!
CREATE OR REPLACE TRIGGER SYSTEM.Product_Price_Check
BEFORE UPDATE ON SYSTEM.product FOR EACH ROW
DECLARE
min_price NUMBER(19,4);
new_price NUMBER(19,4);
BEGIN
SELECT (StandardCost*1.2)
INTO min_price
FROM SYSTEM.product
WHERE ProductID = :new.ProductID;
SELECT ListPrice
INTO new_price
FROM SYSTEM.product
WHERE ProductID = :new.ProductID;
IF new_price < min_price THEN
ROLLBACK;
--DBMS_OUTPUT.PUT_LINE('the price can’t be below '||CAST(min_price as VARCHAR(25)));
--RAISE VALUE_ERROR;
--ELSE
--DBMS_OUTPUT.PUT_LINE('Price was successfully changed');
END IF;
END;
I suppose this is a homework assignment and you have been told to use a trigger. Long years on this site have taught me that teachers love setting assignments which demand the misuse of triggers.
In real life the only correct way to enforce such a rule is with a check constraint:
alter table product add constraint price_check
check (standard_cost * 1.2 >= min_price)
Show your error.
Probably your trigger is mutating
The session that issued the triggering statement cannot query or modify a mutating table. This restriction prevents a trigger from seeing an inconsistent set of data.
As others have pointed out, you should not be creating objects such as tables or triggers in the SYSTEM schema. Create yourself a user, grant it the necessary privileges (good practice), and use that user and its schema for development purposes.
Next - in a row triggers (one with FOR EACH ROW in it) you cannot access the table upon which the trigger is defined, which in this case is the PRODUCT table. Fortunately, you don't really need to. The values you want are already in the :OLD or :NEW pseudo-rows - I'm guessing here that you really want to use the :NEW values:
CREATE OR REPLACE TRIGGER PRODUCT_PRICE_CHECK
BEFORE UPDATE ON PRODUCT
FOR EACH ROW
DECLARE
nMin_price NUMBER := :NEW.STANDARD_COST * 1.2;
BEGIN
IF :NEW.LISTPRICE < nMin_price THEN
DBMS_OUTPUT.PUT_LINE('List price can’t be below '|| nMin_price);
RAISE VALUE_ERROR;
END PRICE_CHECK;
Also, you can't execute a ROLLBACK or COMMIT in a trigger - Oracle doesn't allow this to happen.
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;
Suppose I have the following trigger :
create or replace trigger trigInsertSaloane before insert on saloane
for each row
declare
myExcp exception;
pragma exception_init (myExcp,-20005);
begin
for i in (select * from saloane) loop
if(:new.numar_salon=i.numar_salon) and (trim(upper(:new.nume_sectie))=trim(upper(i.nume_sectie))) then
raise myExcp;
end if;
end loop;
exception when myExcp then dbms_output.put_line('Record exists');
end;
/
All I want is to not insert the row if exception is raised, so something like rollback. In my case if exception is raised and caught, the line is also inserted. I don`t want that. Also I want to make that in a pretty way, by showing up a message and not getting any errors.How to make it?
ok.. a few points.
1) you need to raise an exception from a trigger for the insert will fail and not be inserted. so either do not catch your exception or re RAISE it again.
2) using dbms_output.put_line() will only display a message if the user/client has it turned on.
3) you do not need to loop over your cursor. adding a where clause is more efficient
4) your trigger will not work.. it will throw
ORA-04091: table SALOANE is mutating, trigger/function may not see it if you insert more then 1 row at a time. (try insert into saloane select * from saloane )
5) it may just be your example.. it looks like you could more simply use a unique constraint on the given columns to enforce this requirement.
Adding few more points as mentined in other answer.
Since you are handling the EXCEPTION (PRETTY WELL :p ) in the
Trigger so ideally Trigger event execution is successful so
INSERT/UPDATE/DELETE will happen.
What you need here is to RAISE some kind of exception which will
force the Trigger to fails with the Exception so
in this you need to have RAISE_APPLICATION_ERROR condition to
handle this.
As mentioned above UNIQUE Key constraint will be your best friend to
work in this case.
I created the trigger to update the oracle data base table after insert.
CREATE OR REPLACE TRIGGER Update_ACU
AFTER INSERT ON TBL_ACU
FOR EACH ROW
BEGIN
UPDATE TBL_ACU
SET CURRENCY = 'XXX'
WHERE ACCOUNT like '%1568';
END ;
I inserted record as
insert into TBL_ACU values('23','USD','1231568');
I am getting table ORACLE Mutating trigger error.
Please help me how to resolve this.
It would be better to use BEFORE INSERT trigger to do this.
Try like this,
CREATE OR REPLACE
TRIGGER update_acu
BEFORE INSERT ON tbl_acu
FOR EACH ROW
WHEN (NEW.ACCOUNT LIKE '%1568')
BEGIN
:NEW.currency := 'XXX';
END ;
Well, you cannot modify the table from the trigger if the trigger is called upon modification of that table. There are various solutions to this problem including an AFTER STATEMENT trigger and caching modifications in some collection defined in PL/SQL PACKAGE, howewer in your situation I'd rather change the body of your trigger to this:
BEGIN
IF :NEW.ACCOUNT LIKE '%1568' THEN
:NEW.CURRENCY := 'XXX';
END IF;
END;
You can use the :NEW and :OLD variables inside the trigger, which identify the new and old values of the record accordingly. Modifying values of the :NEW record will cause changes in data actually inserted to the database.
I have an AFTER INSERT OR UPDATE OR DELETE trigger that I'm writing to store every record revision that occurs in a certain table, by copying the INSERT and UPDATE :NEW values into a mirror table, and for DELETE the :OLD values.
I could un-clutter my code considerably by conditionally passing either the :NEW or :OLD record into a procedure which would then do the insert into my history table. Unfortunately I cannot seem to find a way to pass the entire :OLD or :NEW record.
Am I missing something or is there no way to avoid enumerating every :NEW and :OLD column as I invoke my insert procedure?
I want to do the following:
DECLARE
PROCEDURE LOCAL_INSERT(historyRecord in ACCT.ACCOUNTS%ROWTYPE) IS
BEGIN
INSERT INTO ACCT.ACCOUNTS_HISTORY (ID, NAME, DESCRIPTION, DATE) VALUES (historyRecord.ID, historyRecord.NAME, historyRecord.DESCRIPTION, SYSDATE);
END;
BEGIN
IF INSERTING OR UPDATING THEN
LOCAL_INSERT(:NEW);
ELSE --DELETING
LOCAL_INSERT(:OLD);
END IF;
END;
But I'm stuck doing this:
DECLARE
PROCEDURE LOCAL_INSERT(id in ACCT.ACCOUNTS.ID%TYPE,
name in ACCT.ACCOUNTS.NAME%TYPE,
description in ACCT.ACCOUNTS.DESCRIPTION%TYPE) IS
BEGIN
INSERT INTO ACCT.ACCOUNTS_HISTORY (ID, NAME, DESCRIPTION, DATE) VALUES (id, name, description, SYSDATE);
END;
BEGIN
IF INSERTING OR UPDATING THEN
LOCAL_INSERT(:NEW.ID, :NEW.NAME, :NEW.DESCRIPTION);
ELSE --DELETING
LOCAL_INSERT(:OLD.ID, :OLD.NAME, :OLD.DESCRIPTION);
END IF;
END;
Okay, so it doesn't look like a big difference, but this is just an example with 3 columns rather than dozens.
It isn't. You have to do it yourself through enumeration.
The reasons it can't/doesn't work automatically include:
the :old and :new are default conventions; you can name the :old and :new references to be whatever you want through the REFERENCING clause of the CREATE TRIGGER statement.
you'd have to have a public declaration of a type (through CREATE TYPE or through a package declaration) to be able to use it as an argument to another piece of code.
trigger code is interpreted code, not compiled code.
I don't think it's possible like that. Documentation doesn't mention anything like that.
This would certainly cost performance, but you could try to define your trigger AFTER INSERT and another one BEFORE UPDATE OR DELETE, and in the trigger do something like:
SELECT *
INTO rowtype_variable
FROM accounts
WHERE accounts.id = :NEW.id; -- :OLD.id for UPDATE and DELETE
and then call your procedure with that rowtype_variable.
Use SQL to generate the SQL;
select ' row_field.'||COLUMN_NAME||' := :new.'||COLUMN_NAME||';' from
ALL_TAB_COLUMNS cols
where
cols.TABLE_NAME = 'yourTableName'
order by cols.column_name.
Then copy and paste output.
If you use AFTER trigger you can use rowid as parameter to call procedure
insert into t_hist
select * from t where rowid = r;
If you use BEFORE trigger you will get ORA-04091 mutating table, BUT you solution can be (http://www.dba-oracle.com/t_avoiding_mutating_table_error.htm):
Don't use triggers - The best way to avoid the mutating table error is not to use triggers. While the object-oriented Oracle provides "methods" that are associated with tables, most savvy PL/SQL developers avoid triggers unless absolutely necessary.
Use an "after" or "instead of" trigger - If you must use a trigger, it's best to avoid the mutating table error by using an "after" trigger, to avoid the currency issues associated with a mutating table. For example, using a trigger ":after update on xxx", the original update has completed and the table will not be mutating.
Re-work the trigger syntax - Dr. Hall has some great notes on mutating table errors, and offers other ways to avoid mutating tables with a combination of row-level and statement-level triggers.
Use autonomous transactions - You can avoid the mutating table error by marking your trigger as an autonomous transaction, making it independent from the table that calls the procedure.