How to insert data into table even if trigger fails? - sql

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;
/

Related

Multiple WHEN Statements in Oracle SQL

Can I get some help on below SQL trigger. Something is wrong with using multiple "WHEN" statements.
create or replace TRIGGER TRANS_TASKS_TRIG02
BEFORE INSERT OR UPDATE ON "TASKS"
REFERENCING FOR EACH ROW
WHEN(NEW.STATUS='WIP') BEGIN
IF INSERTING OR UPDATING THEN
:NEW.UPDATED_DATE := NEW_TIME(SYSDATE, 'GMT', 'PDT' );
END IF;
WHEN(NEW.STATUS<>'WIP') BEGIN
IF INSERTING OR UPDATING THEN
:NEW.UPDATED_DATE := NULL;
END IF;
END;
According to Oracle's documentation, you can't have multiple when clauses in a trigger.
You could create to separate triggers:
create or replace TRIGGER TRANS_TASKS_TRIG02_WIP
BEFORE INSERT OR UPDATE ON "TASKS"
REFERENCING FOR EACH ROW
WHEN(NEW.STATUS='WIP') BEGIN
IF INSERTING OR UPDATING THEN
:NEW.UPDATED_DATE := NEW_TIME(SYSDATE, 'GMT', 'PDT' );
END IF;
END;
create or replace TRIGGER TRANS_TASKS_TRIG02_WIP
BEFORE INSERT OR UPDATE ON "TASKS"
REFERENCING FOR EACH ROW
WHEN(NEW.STATUS<>'WIP') BEGIN
IF INSERTING OR UPDATING THEN
:NEW.UPDATED_DATE := NULL;
END IF;
END;
Or have a single trigger with an if statement in it. Note that the if inserting or updating condition is redundant, since the trigger is invoked only before insert or update:
create or replace TRIGGER TRANS_TASKS_TRIG02
BEFORE INSERT OR UPDATE ON "TASKS"
REFERENCING FOR EACH ROW
BEGIN
IF :NEW.STATUS='WIP' THEN
:NEW.UPDATED_DATE := NEW_TIME(SYSDATE, 'GMT', 'PDT' );
ELSIF :NEW.STATUS<>'WIP' THEN
:NEW.UPDATED_DATE := NULL;
END IF;
END;
/

simple trigger/procedure problem for oracle apex sql

im trying to run this audit trail trigger in oracle apex sql but i keep getting the same error and i dont know what im doing wrong.
also i need to do this same trigger to every table in my database... is there a way i can do the same thing through a procedure so as to only having to do it once?
create or replace TRIGGER AUDIT_TRAIL_USERS_TRIG
-- starts on every update, insert or delete command
AFTER INSERT OR DELETE OR UPDATE ON USERS
FOR EACH ROW
DECLARE
-- variable which declares if update, delete or insert process
v_trg_action varchar2(10);
BEGIN
IF updating THEN
-- when update
v_trg_action := 'UPDATE';
ELSIF deleting THEN
-- when delete
v_trg_action := 'DELETE';
ELSIF inserting THEN
-- when insert
v_trg_action := 'INSERT';
ELSE
-- if something else
END IF;
IF v_trg_action IN ('DELETE','UPDATE','INSERT') THEN
-- if v_trg_action is DELETE, UPDATE OR INSERT then insert old table values
INSERT INTO AUDIT_TRAIL
( AUDIT_USER, AUDIT_DATE, AUDIT_ACTION)
VALUES
(UPPER(v('APP_USER')), SYSDATE, v_trg_action);
ELSE
END IF;
-- about the insert command on the audit table
-- for current apex user: v('APP_USER')
-- for date: SYSDATE
-- for sql command: v_trg_action
END AUDIT_TRAIL_USERS_TRIG;
the error im getting (im sure i have more than what its saying to me)is as follows:
Compilation failed, line 16 (03:29:53) The line numbers associated with compilation errors are relative to the first BEGIN statement. This only affects the compilation of database triggers.
PLS-00103: Encountered the symbol "END" when expecting one of the following: ( begin case declare exit for goto if loop mod null pragma raise return select update while with << continue close current delete fetch lock insert open rollback savepoint set sql execute commit forall merge pipe purge json_exists json_value json_query json_object json_array Compilation failed, line 25 (03:29:53) The line numbers associated with compilation errors are relative to the first BEGIN statement. This only affects the compilation of database triggers.
PLS-00103: Encountered the symbol "END" when expecting one of the following: ( begin case declare exit for goto if loop mod null pragma raise return select update while with << continue close current delete fetch lock insert open rollback savepoint set sql execute commit forall merge pipe purge json_exists json_value json_query json_object json_array
IF..ELSE blocks cannot be left empty. If you don't need them remove it, I have added a dummy NULL call for the code to compile.Add appropriate logic as desired, otherwise remove the block.
create or replace TRIGGER AUDIT_TRAIL_USERS_TRIG
-- starts on every update, insert or delete command
AFTER INSERT OR DELETE OR UPDATE ON USERS
FOR EACH ROW
DECLARE
-- variable which declares if update, delete or insert process
v_trg_action varchar2(10);
BEGIN
IF updating THEN
-- when update
v_trg_action := 'UPDATE';
ELSIF deleting THEN
-- when delete
v_trg_action := 'DELETE';
ELSIF inserting THEN
-- when insert
v_trg_action := 'INSERT';
ELSE
-- if something else
NULL;
END IF;
IF v_trg_action IN ('DELETE','UPDATE','INSERT') THEN
-- if v_trg_action is DELETE, UPDATE OR INSERT then insert old table values
INSERT INTO AUDIT_TRAIL
( AUDIT_USER, AUDIT_DATE, AUDIT_ACTION)
VALUES
(UPPER(v('APP_USER')), SYSDATE, v_trg_action);
null;
ELSE
NULL;
END IF;
-- about the insert command on the audit table
-- for current apex user: v('APP_USER')
-- for date: SYSDATE
-- for sql command: v_trg_action
END AUDIT_TRAIL_USERS_TRIG;
Use this code, it will work same like yours
create or replace TRIGGER AUDIT_TRAIL_USERS_TRIG
AFTER INSERT OR DELETE OR UPDATE ON USERS
FOR EACH ROW
DECLARE
BEGIN
IF inserting or updating or deleting THEN
INSERT INTO AUDIT_TRAIL
(AUDIT_USER, AUDIT_DATE, AUDIT_ACTION)
VALUES
(UPPER(v('APP_USER')), SYSDATE, v_trg_action);
END IF;
END AUDIT_TRAIL_USERS_TRIG;

Need to know the resolution for bind variable

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;

Ora-04072: INVALID TRIGGER TYPE

I'm trying to execute the following SQL statement on Oracle 11g. I'm not experienced when it comes to Oracle and I'm not sure why this is failing. This query was provided to me by our developer.
I was attempting to execute this through the SQL worksheet in OEM.
CREATE OR REPLACE TRIGGER TBL_ADMINCOMMAND_TRG BEFORE
INSERT OR UPDATE ON tbl_AdminCommands FOR EACH ROW
BEGIN
IF inserting
AND :new.ADMINCOMMANDID IS NULL THEN
SELECT TBL_ADMINCOMMANDS_SEQ.nextval INTO :new.ADMINCOMMANDID FROM DUAL;
END IF;
END;
ALTER TRIGGER TBL_ADMINCOMMAND_TRG ENABLE;
The code you show works for me, but only as two separate commands:
1)
CREATE OR REPLACE TRIGGER TBL_ADMINCOMMAND_TRG BEFORE
INSERT OR UPDATE ON tbl_AdminCommands FOR EACH ROW
BEGIN
IF inserting
AND :new.ADMINCOMMANDID IS NULL THEN
SELECT TBL_ADMINCOMMANDS_SEQ.nextval INTO :new.ADMINCOMMANDID FROM DUAL;
END IF;
END;
2)
ALTER TRIGGER TBL_ADMINCOMMAND_TRG ENABLE;
Try doing them one at a time.
As an aside, this line:
SELECT TBL_ADMINCOMMANDS_SEQ.nextval INTO :new.ADMINCOMMANDID FROM DUAL;
can be simplified to this in 11G:
:new.ADMINCOMMANDID := TBL_ADMINCOMMANDS_SEQ.nextval;
In fact, the whole trigger can be simplified to:
CREATE OR REPLACE TRIGGER TBL_ADMINCOMMAND_TRG
BEFORE INSERT ON tbl_AdminCommands
FOR EACH ROW
WHEN (NEW.ADMINCOMMANDID IS NULL)
BEGIN
:new.ADMINCOMMANDID := TBL_ADMINCOMMANDS_SEQ.nextval;
END;
If you are using SQL*Plus, you should end your PL/SQL commands with a single forward slash on a line by itself:
CREATE OR REPLACE TRIGGER TBL_ADMINCOMMAND_TRG
BEFORE INSERT OR UPDATE ON tbl_AdminCommands
FOR EACH ROW
BEGIN
IF inserting AND :new.ADMINCOMMANDID IS NULL
THEN
SELECT TBL_ADMINCOMMANDS_SEQ.nextval
INTO :new.ADMINCOMMANDID
FROM DUAL;
END IF;
END;
/
ALTER TRIGGER TBL_ADMINCOMMAND_TRG ENABLE;
Also note that if your trigger uses IF inserting you could do only a trigger BEFORE INSERT.

problem with trigger in oracle

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.