I am trying to write a Trigger which basically updates one table when an entry is made on another table.
CREATE OR REPLACE TRIGGER "DTISCDB_OWNER"."REQUEST_CONTEXT_TR"
AFTER INSERT OR UPDATE ON REQUEST_CONTEXT
FOR EACH ROW
BEGIN
:NEW.REQUEST_DATETIME := SYSDATE;
:NEW.ID := TRUNC(DBMS_RANDOM.VALUE(100000000000000000000000000000000000,999999999999999999999999999999999999));
SELECT bre_conditions_seq.NEXTVAL INTO :OLD.seq_number FROM dual;
SELECT REQUEST_CONTEXT.CURRENT_STATE INTO :NEW.STATE FROM REQUEST_CONTEXT;
SELECT REQUEST_CONTEXT.REQUEST_ID INTO :NEW.REQUEST_ID FROM REQUEST_CONTEXT;
INSERT INTO REQUEST_LIFECYCLES(ID,SEQ_NUMBER,STATE,REQUEST_ID,REQUEST_DATETIME)
VALUES(:NEW.ID,:NEW.seq_number,:NEW.STATE,:NEW.REQUEST_ID,:NEW.REQUEST_DATETIME);
END;
You seem to be confused about what the correlation pseudorows are for and what they hold and can do. It looks like you're treating :NEW as if it is related to the REQUEST_LIFECYCLES table you are inserting into inside the trigger, and :OLD as if it is related to the REQUEST_CONTEXT row that has been inserted or updated and caused the trigger to fire.
Both OLD and NEW refer to the table the trigger is against, REQUEST_CONTEXT. If the trigger was fired by an update then OLD has the pre-update values for the affected row; if it was fired by an insert then it is empty as there is no old state. Either way NEW has the current state, with the newly-inserted or post-update values. You can't change the OLD values, and it doesn't make sense to change the NEW values in an 'after' trigger. You also don't need to query the table the trigger is fired against, as the NEW pseudorow already makes that information available.
So if you are trying to use the inserted/updated values in REQUEST_CONTEXT to create a row in REQUEST_LIFECYCLES, you would do something like:
CREATE OR REPLACE TRIGGER "DTISCDB_OWNER"."REQUEST_CONTEXT_TR"
AFTER INSERT OR UPDATE ON REQUEST_CONTEXT
FOR EACH ROW
BEGIN
INSERT INTO REQUEST_LIFECYCLES(ID, SEQ_NUMBER, STATE, REQUEST_ID,
REQUEST_DATETIME)
VALUES(TRUNC(DBMS_RANDOM.VALUE(100000000000000000000000000000000000, 999999999999999999999999999999999999)),
bre_conditions_seq.NEXTVAL, :NEW.CURRENT_STATE, :NEW.REQUEST_ID,SYSDATE);
END;
/
I'm assuming you wanted to set the 'lifecycle' SEQ_NUMBER value from the trigger, despite you trying to set the :OLD value - hopefully the old reference was a mistake. If you were trying to set that value in both REQUEST_CONTEXT and REQUEST_LIFECYCLES you would need a before insert/update trigger instead, and would set :NEW.SEQ_NUMBER rather than the :OLD value, before using it in the values clause.
As Justin said using a random value for the ID is rather strange, not least because it won't be unique, and a sequence is much more common. You may actually want the ID from the inserted/updated row, in which case you can just refer to :NEW.ID in the values clause instead of generating a new value. (It's also possible you are trying to set that ID in both REQUEST_CONTEXT and REQUEST_LIFECYCLES too, but that would be even stranger, and you'd need a before-insert/update trigger to do that anyway).
Related
I am trying to make an upsert trigger on ORACLE via PL/SQL by checking some examples, i am doing fine, i think it is the last step i should only configure. My requirement is that :
A system that will insert to that field will remain one column always null, so i will read column value from another table, then upsert it with inclusion of that value.
d2c_region_locale_config holds d2c_is_active value, so i firstly read that value regarding to locale condition then trigger inserts or updates to table with addition of this value on active_for_d2c column.(for update i am using locale and country columns as it is shown on where clause, they are not PK but has not null condition)
So i've created this trigger:
CREATE OR REPLACE TRIGGER BL_PIM_LOCALE_COUNTRY
BEFORE INSERT OR UPDATE ON PIM_LOCALE_COUNTRY REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
l_active_for_d2c INTEGER;
BEGIN
if :NEW.active_for_d2c is null then
DELETE from pim_locale_country where active_for_d2c is null;
select distinct(d2c_isactive) into l_active_for_d2c from d2c_region_locale_config where d2c_locale= :NEW.locale;
UPDATE pim_locale_country
SET locale = :NEW.locale, locale_name = :NEW.locale_name,
country = :NEW.country, country_name = :NEW.country_name, isdummy = :NEW.isdummy,
active_for_d2c = l_active_for_d2c, itextpos = :NEW.itextpos, locale_charset = :NEW.locale_charset,
fallback_locale = :NEW.fallback_locale, default_for_lang = :NEW.default_for_lang, opeclang = :NEW.opeclang
where locale = :NEW.locale and country = :NEW.country;
IF ( sql%notfound ) THEN
INSERT INTO PIM_LOCALE_COUNTRY (locale,locale_name,country,country_name,isdummy,active_for_d2c,itextpos,locale_charset,fallback_locale,default_for_lang,opeclang)
VALUES (:NEW.locale, :NEW.locale_name,:NEW.country,:NEW.country_name,:NEW.isdummy,l_active_for_d2c,:NEW.itextpos,:NEW.locale_charset,:NEW.fallback_locale,:NEW.default_for_lang,:NEW.opeclang);
END IF;
end if;
END;
It currently does the job, reads value and inserts or updates the existing locale-country couple for other values. But critical thing is that, table always has one "null" value(Please check screenshot), even that i run delete statement at the beginning on my trigger. So my question would be how to delete, or how to make this approach on trigger side ?
Many thanks for answers!
Trigger before insert doesn't block insert itself, so you insert that record twice. That is, once your trigger done its work (inserted or updated record), oracle will proceed with insert (or update) using values that stand in NEW record of your trigger. If trigger modifies NEW., it will be stored as you changed it, but if trigger inserts something itself, you can get more records.
You can use instead of insert or update triggers, and then oracle will not run its own inserts/updates after trigger finishes.
But more common way for 1-record triggers is to modify fields in NEW, for this case field NEW.d2c_is_active.
It looks like this (possible typos, please check)
CREATE OR REPLACE TRIGGER BL_PIM_LOCALE_COUNTRY
BEFORE INSERT OR UPDATE ON PIM_LOCALE_COUNTRY REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
BEGIN
if :NEW.active_for_d2c is null then
select d2c_isactive
into :NEW.active_for_d2c
from d2c_region_locale_config
where d2c_locale= :NEW.locale and rownum<=1;
end if;
END;
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;
Is it possible to create a generic (not table-specific) trigger in Postgres 9.5 that would perform on instead of update that converts the update into an insert?
Basically what I want to do is (pseudocode):
sql
instead of UPDATE on TG_TABLE_NAME INSERT on TG_TABLE_NAME
I know I can create a very table-specific trigger that maps each value into an insert statement. What I'm trying to do is get away from creating this trigger on every single table.
It is a bit of an oddball idea (nothing personal), but how about this:
CREATE FUNCTION not_update_but_insert() RETURNS trigger AS $$
BEGIN
INSERT INTO TG_TABLE_NAME -- Do an INSERT...
SELECT NEW.*; -- ... using the values from the row to be updated
RETURN NULL; -- Fail the UPDATE
END;
$$ LANGUAGE plpgsql;
Obviously this would not work for any table that has a PRIMARY KEY or other UNIQUE constraints. You do have to CREATE TRIGGER x BEFORE UPDATE for every table this would apply to, so analyze your table structure before creating the trigger.
There is obviously a work-around - at least for the PKs based on a sequence - by examining the information_schema for "safe" columns in TG_TABLE_NAME and then assembling their names into strings to splice into the INSERT statement (column list of main statement and select list). Just leave the columns with sequences or appropriate default values out. This, however, does not address UNIQUE constraints that have no obvious replacement (like a user name or an email address).
I have table A and there is a column name COL_A.
I want that if someone change the value, lets say from 1 to 'X' (not costant) that the trigger will change it back from 'X' to 1.
SQLite does not support changing the new column values.
The only way to change a column in a trigger would be to run an UPDATE command,
but that would run the trigger again.
What you can do is to prevent changing the column in the first place:
CREATE TRIGGER IF NOT EXISTS prevent_col_a_change
BEFORE UPDATE OF col_a ON a
BEGIN
SELECT RAISE(ABORT, 'COL_A must not be changed');
END;
UPDATE trigger is a good solution for your case. Just set old value to the new value, that will lead to behaviour you want.
For example:
CREATE OR REPLACE TRIGGER orders_before_update
BEFORE UPDATE
ON orders
FOR EACH ROW
BEGIN
:new.CreatedAt:= :old.CreatedAt;
END;
How do I write an Oracle trigger, than when a user deletes a certain record, the delete doesnt actually happen, but instead performs an update on those rows and sets the status of the record to 'D'?
I tried:
create or replace
trigger DELFOUR.T4M_ITEM_ONDELETE
before delete on M_ITEM_H
FOR EACH ROW
BEGIN
UPDATE
M_ITEM_H
SET
ITEM_STAT = 'D'
WHERE
CUST_CODE = 'TEST'
AND ITEM_CODE = 'GDAY'
;
raise_application_error(-20000,'Cannot delete item');
END;
But I am getting mutating table errors. Is this possible?
If you really need a trigger, the more logical approach would be to create a view, create an INSEAD OF DELETE trigger on the view, and to force the applications to issue their deletes against the view rather than against the base table.
CREATE VIEW vw_m_item_h
AS
SELECT *
FROM m_item_h;
CREATE OR REPLACE TRIGGER t4m_item_ondelete
INSTEAD OF DELETE ON vw_m_item_h
FOR EACH ROW
AS
BEGIN
UPDATE m_item_h
SET item_stat = 'D'
WHERE <<primary key>> = :old.<<primary key>>;
END;
Better yet, you would dispense with the trigger, create a delete_item procedure that your application would call rather than issuing a DELETE and that procedure would simply update the row to set the item_stat column rather than deleting the row.
If you really, really, really want a solution that involves a trigger on the table itself, you could
Create a package with a member that is a collection of records that map to the data in the m_item_h table
Create a before delete statement-level trigger that empties this collection
Create a before delete row-level trigger that inserts the :old.<<primary key>> and all the other :old values into the collection
Create an after delete statement-level trigger that iterates through the collection, re-inserts the rows into the table, and sets the item_stat column.
This would involve more work than an instead of trigger since you'd have to delete and then re-insert the row and it would involve way more moving pieces so it would be much less elegant. But it would work.
First of all the trigger you wrote would throw a mutating table error. Technically what you are asking is not possible i.e. delete wouldn't delete but rather update, unless you raise an exception in the middle which could be an ugly way of doing it. I would think users using some sort of application front end which lets them delete data using a delete button, so you may use an update statement there instead of a delete statement.
Another option would be to create a log table, where you could insert the record before deleting it from the actual table and then join the log table with the actual table to retrieve deleted records. Something like-
CRETAE TABLE M_ITEM_H_DEL_LOG as SELECT * FROM M_ITEM_H WHERE 1=2;
And then
create or replace
trigger DELFOUR.T4M_ITEM_ONDELETE
before delete on M_ITEM_H
FOR EACH ROW
BEGIN
INSERT INTO
M_ITEM_H_DEL_LOG
VALUES (:old.col1, :old.col2,.....) --col1, col2...are columns in M_ITEM_H
;
END;