CREATE OR REPLACE TRIGGER transaction_type_af_insert
AFTER INSERT ON transaction_type
FOR EACH ROW
DECLARE
aatype VARCHAR2(30);
action VARCHAR2(30);
BEGIN
CASE
WHEN INSERTING THEN
action := 'After_Insert_transaction_type';
END CASE;
INSERT INTO transaction_type_log_history (aatype, action) VALUES
(:new.aatype, action);
END;
/
SHOW ERROR;
I get below error:
LINE/COL|ERROR
9/69 |PLS-00049: bad bind variable 'NEW.AATYPE'
The transaction_type table already has one 'type' column, so the code has been modified to take that value into the log table for the trigger. Corrected code which compiles without errors:
CREATE OR REPLACE TRIGGER transaction_type_af_insert
AFTER INSERT
ON transaction_type
FOR EACH ROW
DECLARE
action VARCHAR2(30);
BEGIN
CASE
WHEN INSERTING THEN
action := 'After_Insert_transaction_type';
END CASE;
INSERT INTO transaction_type_log_history (type, action)
VALUES (:new.type, action);
END;
/
SHOW ERROR;
Related
HI I am trying to prevent inserting the same DATE for the same user. Trigger generates an error stating that it is not mutable. Is there any way to fix it?
CREATE OR REPLACE TRIGGER fake_trig
BEFORE INSERT ON newtable
FOR EACH ROW
DECLARE
testtr1 NUMBER;
testtr1 Date;
testtr1 EXCEPTION;
BEGIN
SELECT
CHECKTB1,
CHECKTTB2
INTO
testtr1,
testtr1
FROM newtable
WHERE :NEW.checktb1 = CHECKTB1;
IF :new.checkttb2 = testtr1 THEN
RAISE testtr1;
END IF;
EXCEPTION
WHEN testtr1 THEN
Raise_application_error(-20300,'not working');
WHEN NO_DATA_FOUND THEN
NULL;
END;
A trigger is inappropriate for this task. This is something that should be enforced using a UNIQUE constraint:
ALTER TABLE newtable
ADD CONSTRAINT newtable__checktb1_checkttb2__u UNIQUE (checktb1, checkttb2);
If you did want to use a trigger (don't, this should be handled by a constraint) then all you need to do is fix the variables so they are not all named identically:
CREATE OR REPLACE TRIGGER fake_trig
BEFORE INSERT ON newtable
FOR EACH ROW
DECLARE
testtr1 NUMBER;
testtr2 Date;
testtr3 EXCEPTION;
BEGIN
SELECT CHECKTB1,
CHECKTTB2
INTO testtr1,
testtr2
FROM newtable
WHERE :NEW.checktb1 = CHECKTB1;
IF :new.checkttb2 = testtr2 THEN
RAISE testtr3;
END IF;
EXCEPTION
WHEN testtr3 THEN
Raise_application_error(-20300,'not working');
WHEN NO_DATA_FOUND THEN
NULL;
END;
/
Note: you should give variables meaningful names so that you can tell what they each represent.
db<>fiddle here
Oracle 11.1
I have custom logging table where I insert data:
CREATE TABLE log_table
(
message VARCHAR2(255),
created_by VARCHAR2(40) NOT NULL,
created_at DATE NOT NULL,
);
I have a trigger that runs on a specific table which does some checkings. My problem is: when the trigger fails, I want to be able to log some data into the log_table.
Trigger:
CREATE OR REPLACE TRIGGER my_trigger
FOR INSERT OR UPDATE OF column
ON my_table
COMPOUND TRIGGER
BEFORE STATEMENT IS
BEGIN
// code
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
IF (/*condition for failing*/) THEN
EXECUTE IMMEDIATE 'INSERT INTO mesaj_ama VALUES (:my_message, :my_user, :my_data)'
USING 'custom error message', SYS.LOGIN_USER, SYSDATE;
RAISE_APPLICATION_ERROR(-20001, 'some error');
END IF;
END BEFORE EACH ROW;
END my_trigger;
/
The following code doesn't work. I tried to use EXECUTE IMMEDIATE maybe to force it, but didn't work. I know that in case of an error, there is automatically a table rollback (which means that the INSERT command is cancelled), but I need a way to do this. Any help?
The concept you're looking for is an Autonomous Trasnaction, eg
CREATE OR REPLACE TRIGGER log_sal
BEFORE UPDATE OF salary ON emp FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO log (
log_id,
up_date,
new_sal,
old_sal
)
VALUES (
:old.employee_id,
SYSDATE,
:new.salary,
:old.salary
);
COMMIT;
END;
Yes, PRAGMA AUTONOMOUS_TRANSACTION seems to be the answer. Here is the working code:
Defined a procedure for logging:
CREATE OR REPLACE PROCEDURE log_error(p_error log_table.message % TYPE) AS
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
INSERT INTO log_table
VALUES (p_error, SYS.LOGIN_USER, SYSDATE);
COMMIT;
END;
and the trigger which calls the procedure:
CREATE OR REPLACE TRIGGER my_trigger
FOR INSERT OR UPDATE OF column
ON my_table
COMPOUND TRIGGER
BEFORE STATEMENT IS
BEGIN
// code
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
IF (/*condition for failing*/) THEN
log_error('custom error message');
RAISE_APPLICATION_ERROR(-20001, 'custom error message');
END IF;
END BEFORE EACH ROW;
END my_trigger;
/
I have mostly the same code for a trigger that occurs before a INSERT or UPDATE,
and instead of creating 2 triggers, is there any way to know the type of event occurring dynamically inside the trigger ?
Example:
CREATE OR REPLACE FUNCTION UpsertClientLog() RETURNS TRIGGER AS $$
DECLARE action_type VARCHAR(255);
BEGIN
IF (event == 'INSERT') THEN
action_type = 'Create'
ELSE
action_type = 'Update'
END IF;
-- Same code from here on out ..
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER UPSERT_CLIENT_LOG BEFORE INSERT OR UPDATE ON client
FOR EACH ROW EXECUTE PROCEDURE UpsertClientLog();
You can use special variable TO_OP:
TG_OP
Data type text; a string of INSERT, UPDATE, DELETE, or TRUNCATE telling for which operation the trigger was fired.
I have a trigger function in postgresql which will insert rows in the audit table on INSERT, UPDATE and DELETE operations. In my tables, there is a column called audit_id and I need to write the ID of inserted audit row in this field. This is my function
CREATE OR REPLACE FUNCTION my_audit_trigger()
RETURNS trigger LANGUAGE plpgsql
AS $function$
declare
audit_pk bigint;
begin
IF TG_OP = 'INSERT'
THEN
INSERT INTO audit.table_audit (rel_id, table_name, operation, after)
VALUES (TG_RELID, TG_TABLE_NAME, TG_OP, to_jsonb(NEW)) returning id into audit_pk;
NEW.audit_id := audit_pk;
RETURN NEW;
ELSIF TG_OP = 'UPDATE'
THEN
IF NEW != OLD THEN
INSERT INTO audit.table_audit (rel_id, table_name, operation, before, after)
VALUES (TG_RELID, TG_TABLE_NAME, TG_OP, to_jsonb(OLD), to_jsonb(NEW)) returning id into audit_pk;
END IF;
NEW.audit_id := audit_pk;
RETURN NEW;
ELSIF TG_OP = 'DELETE'
THEN
INSERT INTO audit.table_audit (rel_id, table_name, operation, before)
VALUES (TG_RELID, TG_TABLE_NAME, TG_OP, to_jsonb(OLD)) returning id into audit_pk;
OLD.audit_id := audit_pk;
RETURN OLD;
END IF;
end;
$function$;
As a result, when inserting or updating my table rows, I get back the audit id of the corresponding operation, but when I run DELETE command, I get back the audit ID of the previous operation, not of the DELETE itself. So I guess the problem is in OLD.audit_id := audit_pk;
More specifically, I run for example INSERT INTO table VALUES (this, that) RETURNING audit_id and I get back audit_id of the INSERT operation.
After, when running DELETE FROM table WHERE id = sth RETURNING audit_id I get audit_id of the INSERT operation, not of the DELETE.
Any help is appreciated, thank you.
P.S. This is how I create trigger
CREATE TRIGGER table_trigger
BEFORE INSERT OR UPDATE OR DELETE
ON table
FOR EACH ROW
EXECUTE PROCEDURE my_audit_trigger();
I have similar problem. That seems that PG just does not support modification of OLD now, but, probably, this feature will be included into TODO list.
Currently you can modify only NEW for INSERT and UPDATE statements
For details look into this mail thread: Does 'instead of delete' trigger support modification of OLD
the problem is this :
I implemented a trigger on the table called CLAN_AFFILIATI that increases (if inseriemento) and decreases (in case of cancellation) an attribute (NUMAFFILIATI) of another table called CLAN. what I would do is block the update NUMAFFILIATI of Clan by the user and had thought to r another trigge on CLAN that did this:
trigger on CLAN_AFFILIATI(CLAN VARCHAR,AFFILIATO VARCHAR,RUOLO VARCHAR)
CREATE OR REPLACE TRIGGER "AggiornamentoNumAffiliati"
AFTER INSERT OR DELETE ON CLAN_AFFILIATI
FOR EACH ROW
DECLARE
CLAN_APPARTENENZA VARCHAR(20);
BEGIN
IF INSERTING THEN
SELECT NOME INTO CLAN_APPARTENENZA
FROM CLAN
WHERE NOME=:new.CLAN;
UPDATE CLAN
SET NUMAFFILIATI=NUMAFFILIATI+1
WHERE CLAN_APPARTENENZA=NOME;
ELSE
SELECT NOME INTO CLAN_APPARTENENZA
FROM CLAN
WHERE NOME=:old.CLAN;
UPDATE CLAN
SET NUMAFFILIATI=NUMAFFILIATI-1
WHERE CLAN_APPARTENENZA=NOME;
END IF;
END;
trigger on CLAN (NAME VARCHAR ,NUMAFFILIATI INTEGER)
CREATE OR REPLACE TRIGGER "ModificaNumAffiliati"
BEFORE INSERT OR UPDATE OF NUMAFFILIATI ON CLAN
FOR EACH ROW
DECLARE
CONT NUMBER:=0;
BEGIN
IF INSERTING THEN
IF :new.NUMAFFILIATI <> 0 THEN
RAISE_APPLICATION_ERROR(-20016,'NUMERO ERRATO');
END IF;
ELSE
SELECT COUNT(*) INTO CONT
FROM CLAN_AFFILIATI
WHERE :old.NOME=CLAN;
IF CONT <> :new.NUMAFFILIATI THEN
RAISE_APPLICATION_ERROR(-20017,'NUMERO ERRATO');
END IF;
END IF;
END;
but so I'm doing is reporting an error:
error ORA-04091: Table ANTONIO.CLAN_AFFILIATI is being modified, the trigger / function can not read
ORA-06512: at "ANTONIO.ModificaNumAffiliati", line 10
ORA-04088: error during execution of trigger 'ANTONIO.ModificaNumAffiliati'
ORA-06512: at "ANTONIO.AggiornamentoNumAffiliati", line 12
ORA-04088: error during execution of trigger 'ANTONIO.AggiornamentoNumAffiliati
how can I solve this problem ....
This is propably solution:
I tested it with this sample tables:
CREATE TABLE CLAN_AFFILIATI(CLAN VARCHAR2(100),AFFILIATO VARCHAR2(100),RUOLO VARCHAR2(100));
CREATE TABLE CLAN (NOME VARCHAR2(100) ,NUMAFFILIATI NUMBER(10));
You need this helper package.
CREATE OR REPLACE PACKAGE STORE_NOMES
AS
TYPE record_nomes IS RECORD (
nome VARCHAR2(100),
operation VARCHAR2(100) -- insert or delete
);
TYPE array_type_nomes IS TABLE OF record_nomes INDEX BY BINARY_INTEGER;
g_array_nomes array_type_nomes;
END STORE_NOMES;
/
Trigger on CLAN table:
CREATE OR REPLACE TRIGGER MODIFICANUMAFFILIATI
BEFORE INSERT OR UPDATE OF NUMAFFILIATI ON CLAN
FOR EACH ROW
DECLARE
l_CONT NUMBER:=0;
BEGIN
IF INSERTING THEN
-- prevent inserting <> 0
IF :new.NUMAFFILIATI <> 0 THEN
RAISE_APPLICATION_ERROR(-20016,'NUMERO ERRATO');
END IF;
ELSE
SELECT COUNT(*) INTO l_CONT
FROM CLAN_AFFILIATI
WHERE CLAN = :old.NOME;
IF l_CONT <> :new.NUMAFFILIATI THEN
RAISE_APPLICATION_ERROR(-20017,'NUMERO ERRATO');
END IF;
END IF;
END;
/
Before statement trigger on CLAN_AFFILIATI table:
CREATE OR REPLACE TRIGGER TRG_CLAN_AFFILIATI_BEFORE_STMT
BEFORE INSERT OR DELETE
ON CLAN_AFFILIATI
DECLARE
BEGIN
STORE_NOMES.g_array_nomes.DELETE;
END;
/
After statement trigger on CLAN_AFFILIATI table:
CREATE OR REPLACE TRIGGER TRG_CLAN_AFFILIATI_AFTER_STMT
AFTER INSERT OR DELETE
ON CLAN_AFFILIATI
DECLARE
BEGIN
FOR i IN STORE_NOMES.g_array_nomes.FIRST..STORE_NOMES.g_array_nomes.LAST LOOP
IF(STORE_NOMES.g_array_nomes(i).operation = 'INSERTING') THEN
UPDATE CLAN
SET NUMAFFILIATI=NUMAFFILIATI+1
WHERE NOME = STORE_NOMES.g_array_nomes(i).NOME;
ELSIF(STORE_NOMES.g_array_nomes(i).operation = 'DELETING') THEN
UPDATE CLAN
SET NUMAFFILIATI=NUMAFFILIATI-1
WHERE NOME = STORE_NOMES.g_array_nomes(i).NOME;
END IF;
END LOOP;
END;
/
Row Insert/Delete trigger on CLAN_AFFILIATI table:
CREATE OR REPLACE TRIGGER AGGIORNAMENTONUMAFFILIATI
BEFORE INSERT OR DELETE ON CLAN_AFFILIATI
FOR EACH ROW
DECLARE
l_CLAN_APPARTENENZA VARCHAR(20);
BEGIN
IF INSERTING THEN
SELECT NOME INTO l_CLAN_APPARTENENZA
FROM CLAN
WHERE NOME = :new.CLAN;
STORE_NOMES.g_array_nomes(STORE_NOMES.g_array_nomes.COUNT).nome := :new.CLAN;
STORE_NOMES.g_array_nomes(STORE_NOMES.g_array_nomes.LAST).operation := 'INSERTING';
ELSE
SELECT NOME INTO l_CLAN_APPARTENENZA
FROM CLAN
WHERE NOME = :old.CLAN;
STORE_NOMES.g_array_nomes(STORE_NOMES.g_array_nomes.COUNT).nome := :old.CLAN;
STORE_NOMES.g_array_nomes(STORE_NOMES.g_array_nomes.LAST).operation := 'DELETING';
END IF;
END;
/
Now working this (without ORACLE-EXCEPTION):
INSERT INTO CLAN(NOME, NUMAFFILIATI) VALUES('Antonio', 0);
INSERT INTO CLAN_AFFILIATI(CLAN,AFFILIATO,RUOLO) values('Antonio','Affiliato1','Ruolo1');
INSERT INTO CLAN_AFFILIATI(CLAN,AFFILIATO,RUOLO) values('Antonio','Affiliato2','Ruolo2');
Change the first trigger "AggiornamentoNumAffiliati" so that it doesn't immediately try to update clan, but stores the name (NOME) in a PL/SQL-Table within a Package; then, you make an AFTER INSERT OR DELETE (but without the FOR EACH ROW clause) trigger that reads the PL/SQL table from the package and updates the CLANs accordingly.
I don't have my developer tools with me, but it looks to me as though your getting yourself into a cyclic dependency issue of sorts. When your CLAN_AFFILIATI trigger is raised, in it you do an update of CLAN which calls the second trigger, which has a select from the CLAN_AFFILIATI table in the ELSE block.
Maybe the before insert (first query), and after insert(second query) have an affect also.
ORA-04091 is also known as a "mutating table" error - basically, row triggers cannot query or alter the table on which the trigger operates.
#Martin's answer is the classic description of how to work around this issue, but it you're on Oracle 11+ you can use a compound trigger to do the same thing.
Share and enjoy.