Oracle Trigger To Check Values On Insert & Update - oracle

I am trying to check a value-gap crossed with each other or not. Let me explain with a quick scenario.
Rec 1 : Company AA : Distance Gap -> 100 - 200
Rec 2 : Company AA : Distance Gap -> 200 - 300 VALID
Rec 3 : Company AA : Distance Gap -> 250 - 450 INVALID
Rec 4 : Company JL : Distance Gap -> 250 - 450 VALID
Rec 3 Invalid because it is between REC 2's distance values.
Rec 4 Valid because company is different
Thus I wrote a trigger to prevent it.
create or replace trigger ab_redemption_billing_mpr
after insert or update on redemption_billing_mpr
for each row
declare
v_is_distance_range_valid boolean;
begin
v_is_distance_range_valid := p_redemption_billing.is_distance_range_valid(:new.id,
:new.redemption_billing_company,
:new.distance_min,
:new.distance_max,
'redemption_billing_mpr');
if not v_is_distance_range_valid then
raise_application_error(-20001, 'This is a custom error');
end if;
end ab_redemption_billing_mpr;
FUNCTION:
function is_distance_range_valid(p_id in number,
p_company in varchar2,
p_distance_min in number,
p_distance_max in number,
p_table_name in varchar2) return boolean is
d_record_number number;
begin
execute immediate 'select count(*) from ' || p_table_name || ' r
where r.redemption_billing_company = :1
and (:2 between r.distance_min and r.distance_max or :3 between r.distance_min and r.distance_max)
and r.id = nvl(:4, id)'
into d_record_number
using p_company, p_distance_min, p_distance_max, p_id;
if (d_record_number > 0) then
return false;
else
return true;
end if;
end;
is_distance_range_valid() works just as I expected. If it returns false, that means range check is not valid and don't insert or update.
When I create a scenario to catch this exception, oracle gives
ORA-04091: table name is mutating, trigger/function may not see it
and it points select count(*) line when I click debug button.
I don't know why I am getting this error. Thanks in advance.

Just check whether the below approach resolves your issue
Create a log table with columns required for finding the is_distance_range_valid validation and In your trigger insert into this log tab.The trigger on main table has to be a before insert or update trigger
Create a trigger on this log table and do the validation in the log table trigger and raise error.The trigger on log table has to be an after insert or update trigger.
Also this will only work if the row is already existing in the main table and it is not part of the current insert or update. If validation of the current insert or update is required then we need to use the :new.column_name to do the validation
Test:-
create table t_trg( n number);
create table t_trg_log( n number);
create or replace trigger trg_t
before insert or update on t_trg
for each row
declare
l_cnt number;
begin
insert into t_trg_log values(:new.n);
end trg_t;
create or replace trigger trg_t_log
after insert or update on t_trg_log
for each row
declare
l_cnt number;
begin
select count(1) into l_cnt from t_trg
where n=:new.n;
if l_cnt > 0 then
raise_application_error(-20001, 'This is a custom error');
end if;
end trg_t_log;
The first time I insert it doesn't throw error
insert into t_trg values(7);
1 row inserted.
The second time I execute, I get the custom error
insert into t_trg values(7);
Error report -
ORA-20001: This is a custom error
ORA-06512: at "TRG_T_LOG", line 7
ORA-04088: error during execution of trigger 'TRG_T_LOG'
ORA-06512: at "TRG_T", line 4
ORA-04088: error during execution of trigger 'TRG_T'
Update:-Using compound trigger the error is thrown in the first time the insert is done since it also takes into account the current row that we are updating or inserting while doing the SELECT

First I want to thanks to psaraj12 for his effort.
I found solution. Oracle forces us to not use select query before each row.
Oracle trigger error ORA-04091
So I wrote this and it works.
create or replace trigger ab_redemption_billing_mpr
for insert or update on redemption_billing_mpr
compound trigger
v_is_distance_range_valid boolean;
id redemption_billing_mpr.id%type;
redemption_billing_company redemption_billing_mpr.redemption_billing_company%type;
distance_min redemption_billing_mpr.distance_min%type;
distance_max redemption_billing_mpr.distance_max%type;
after each row is
begin
id := :new.id;
redemption_billing_company := :new.redemption_billing_company;
distance_min := :new.distance_min;
distance_max := :new.distance_max;
end after each row;
after statement is
begin
v_is_distance_range_valid := p_redemption_billing.is_distance_range_valid(id,
redemption_billing_company,
distance_min,
distance_max,
'redemption_billing_mpr');
if not v_is_distance_range_valid then
raise_application_error(-20001, 'This is a custom error');
end if;
end after statement;
end ab_redemption_billing_mpr;
We must use compound trigger to deal with it.

Related

Trigger created with compilation errors. PL/SQL

I've been searching for a while how to resolve this issue and couldn't find anything helpful.
Why this trigger is created with compilation errors?
The warning im getting
Warning: Trigger created with compilation errors.
CREATE OR REPLACE TRIGGER peope_in_dept
AFTER INSERT OR DELETE OR UPDATE on emp
FOR EACH ROW
BEGIN
VARIABLE v NUMBER;
EXECUTE :v := count_people_in_dept(:new.deptno)
IF :v <= 0 or :v >= 10 THEN
raise_application_error(-20000,'Nieodpowiednia liczba pracownikow');
END IF;
END;
/
Syntax errors. Should be
SQL> CREATE OR REPLACE TRIGGER peope_in_dept
2 AFTER INSERT OR DELETE OR UPDATE
3 ON emp
4 FOR EACH ROW
5 DECLARE
6 v NUMBER;
7 BEGIN
8 v := count_people_in_dept (:new.deptno);
9
10 IF v <= 0
11 OR v >= 10
12 THEN
13 raise_application_error (-20000, 'Nieodpowiednia liczba pracownikow');
14 END IF;
15 END;
16 /
Trigger created.
SQL>
However, I suspect it won't work if count_people_in_dept function counts employees in emp table. Why? Because table is mutating:
SQL> UPDATE emp SET comm = 1 WHERE deptno = 10;
UPDATE emp SET comm = 1 WHERE deptno = 10
*
ERROR at line 1:
ORA-04091: table SCOTT.EMP is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.COUNT_PEOPLE_IN_DEPT", line 6
ORA-06512: at "SCOTT.PEOPE_IN_DEPT", line 4
ORA-04088: error during execution of trigger 'SCOTT.PEOPE_IN_DEPT'
SQL>
Now, it depends on what you really are doing in that function. One way out is to set the function to be an autonomous transaction; another one is to use compound trigger; the third option is to use custom type and a package. Or, modify the process altogether (i.e. avoid the trigger).
Why this trigger is created with compilation errors?
Because you are trying to create a SQL/Plus global bind variable inside a PL/SQL procedure and then using EXECUTE rather than just calling the function (and you missed a ; statement terminator).
The correct syntax would be:
FOR EACH ROW
DECLARE
v NUMBER;
BEGIN
v := count_people_in_dept(:new.deptno);
IF v <= 0 or v >= 10 THEN
Then it would compile. However, it still won't work as the table would be mutating as the trigger is running.
Instead, you can use a statement trigger rather than a row trigger:
CREATE OR REPLACE TRIGGER people_in_dept
AFTER INSERT OR DELETE OR UPDATE on emp
DECLARE
v_count PLS_INTEGER;
BEGIN
SELECT 1
INTO v_count
FROM dept d
LEFT OUTER JOIN emp e
ON (e.deptno = d.deptno)
GROUP BY d.deptno
HAVING COUNT(e.deptno) NOT BETWEEN 1 AND 9
FETCH FIRST ROW ONLY;
raise_application_error(-20000,'Nieodpowiednia liczba pracownikow');
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Do nothing.
NULL;
END;
/
db<>fiddle here
You could also write it as a compound trigger that aggregates the changes in deptno and then only checks the changed values using your function; however, that is a lot more complicated.

Trying to create a trigger to check if there's more than 1 president in my database

I'm trying to find if there's more than 1 president in my database with a trigger and if yes, raise an error, I'm using hr, the table employees and I have to use the job_id to find it. Here's what my code looks like. Thanks!
CREATE OR REPLACE TRIGGER check_pres
BEFORE INSERT OR DELETE OR UPDATE ON employees
FOR EACH ROW
BEGIN
IF ((employees.job_id = 'AD_PRES') > 1)
THEN RAISE_APPLICATION_ERROR(-12345, 'More than one President in database.');
END IF;
END;
You should use Statement Level Trigger instead of Row Level Trigger by removing FOR EACH ROW expression
CREATE OR REPLACE TRIGGER check_pres
BEFORE INSERT OR DELETE OR UPDATE ON employees
DECLARE
v_cnt int;
BEGIN
SELECT COUNT(*) INTO v_cnt FROM employees WHERE job_id = 'AD_PRES';
IF ( v_cnt > 1 ) THEN
RAISE_APPLICATION_ERROR(-20345, 'More than one President in database.');
END IF;
END;
otherwise you'd get mutating error while getting the count value. Btw, the first argument value for RAISE_APPLICATION_ERROR should be between -20999 and -20000

ORA-22903: MULTISET expression not allowed

I have a table which have a field as nested table. I have a trigger on the first table, but it does not work and results on a :ORA-22903: MULTISET expression not allowed
CREATE OR REPLACE TYPE DIA_T as object
(dia varchar2(9),
hora varchar2(6));
CREATE OR REPLACE TYPE DIA_TAB IS TABLE OF DIA_T;
CREATE TABLE T_TALLER(
PK NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY,
...
DIAS DIA_TAB,
FEC_INI DATE NOT NULL,
FEC_FIN DATE NOT NULL,
...
CONSTRAINT CST_PRIMKEY_TALLER PRIMARY KEY (PK),
...
) NESTED TABLE DIAS STORE AS DIAS_TAB;
My trigger is:
CREATE OR REPLACE TRIGGER TRG_TALLER_AU_FEC_FIN AFTER UPDATE OF FEC_FIN ON T_TALLER FOR EACH ROW
BEGIN
FOR REC IN (SELECT T.DIA , T.HORA
FROM TABLE(:NEW.DIAS) T) LOOP
dbms_output.put_line(REC.DIA||' '||REC.HORA);
END LOOP;
END;
When I try something like that:
update t_taller set fec_fin = fec_fin + 20 where pk = 10;
I get the following error:
ORA-22903: MULTISET expression not allowed
ORA-06512: at "ESTAMPAS.TRG_TALLER_AU_FEC_FIN", line 3
ORA-04088: error during execution of trigger 'ESTAMPAS.TRG_TALLER_AU_FEC_FIN'
Can you help me to solve this problem?
Thanks in advance.
Regards,
UPDATE
The trigger I posted is a dummy, but the error i get for the real one is the same, my real trigger is this
CREATE OR REPLACE TRIGGER TRG_TALLER_AU_FEC_FIN AFTER UPDATE OF FEC_FIN ON T_TALLER FOR EACH ROW
BEGIN
IF :NEW.FEC_FIN >= :OLD.FEC_FIN THEN
Pkg_Utilidades.p_ins_taller_clase_grupo(:NEW.PK,(:OLD.FEC_FIN) + 1,:NEW.FEC_FIN,:NEW.DIAS,:NEW.AU_USU_INS);
ELSE
DELETE T_TALLER_CLASE
WHERE FK_TALLER = :NEW.PK
AND FEC_CLASE BETWEEN :NEW.FEC_FIN + 1 AND :OLD.FEC_FIN;
END IF;
END;
Something else to say, I have a "AFTER INSERT" Trigger, and it Works fine:
CREATE OR REPLACE TRIGGER TRG_TALLER_AI_CLASE AFTER INSERT ON T_TALLER FOR EACH ROW
BEGIN
DELETE T_TALLER_CLASE WHERE FK_TALLER = :NEW.PK;
Pkg_Utilidades.p_ins_taller_clase_grupo(:NEW.PK,:NEW.FEC_INI,:NEW.FEC_FIN,:NEW.DIAS,:NEW.AU_USU_INS);
END;
The procedure is:
PROCEDURE p_ins_taller_clase_grupo (p_taller NUMBER,
p_fec_ini DATE,
p_fec_fin DATE,
p_dias DIA_TAB,
p_user VARCHAR2) IS
p_output VARCHAR2(100);
v_dia NUMBER;
BEGIN
FOR REC IN (SELECT p_fec_ini + LEVEL - 1 FECHA,
DECODE(TO_CHAR(p_fec_ini + LEVEL - 1 , 'DAY'),'MONDAY ',1,'TUESDAY ',2,'WEDNESDAY',3,'THURSDAY ',4,'FRIDAY ',5,'SATURDAY ',6,7) DIA
FROM DUAL
CONNECT BY LEVEL <= p_fec_fin - p_fec_ini + 1) LOOP
BEGIN
SELECT D INTO v_dia
FROM (
SELECT decode(upper(T.dia),'LUNES',1,'MARTES',2,'MIERCOLES',3,'MIÉRCOLES',3,'JUEVES',4,'VIERNES',5,'SABADO',6,'SÁBADO',6,7) D
FROM TABLE(p_dias) T
)
WHERE D = REC.DIA;
P_INS_TALLER_CLASE (p_taller,REC.FECHA,Pkg_conf.CST_HORA,p_user,p_output);
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
END;
END LOOP;
END p_ins_taller_clase_grupo;
The insert Works fine:
SQL> insert into t_taller (FK_profesor,fk_danza,fk_local,fk_periodicidad,fec_ini,fec_fin,dias,AU_USU_INS) values (1,1,1,1,to_date('05/01/2019','dd/mm/yyyy'),to_date('27/01/2019','dd/mm/yyyy'),dia_tab(dia_t('SABADO','10:30'),dia_t('DOMINGO','10:30')),'EP_PL01');
1 row created.
SQL> commit;
Commit complete.
SQL> update t_taller set fec_fin = fec_fin + 20 where pk = 24;
update t_taller set fec_fin = fec_fin + 20 where pk = 24
*
ERROR at line 1:
ORA-22903: MULTISET expression not allowed
ORA-06512: at "ESTAMPAS.PKG_UTILIDADES", line 451
ORA-06512: at "ESTAMPAS.TRG_TALLER_AU_FEC_FIN", line 5
ORA-04088: error during execution of trigger 'ESTAMPAS.TRG_TALLER_AU_FEC_FIN'
The line 451 of the package, is inside the procedure, exactly here:
SELECT D INTO v_dia
FROM (
SELECT decode(upper(T.dia),'LUNES',1,'MARTES',2,'MIERCOLES',3,'MIÉRCOLES',3,'JUEVES',4,'VIERNES',5,'SABADO',6,'SÁBADO',6,7) D
FROM TABLE(p_dias) T
)
WHERE D = REC.DIA;
Sorry for not posted all the details from the begining, i wanted to summayrize and show just the error.
Regards
TABLE expression works if you are running a select query on the table itself.
Something like
SELECT T.DIA , T.HORA
FROM T_TALLER ,TABLE(:NEW.DIAS) T
But, you are not allowed to select from the Trigger owner as it leads to "table is mutating" error( ORA-04091 ).
You may instead loop through the nested table column using a simple for loop.
CREATE OR REPLACE TRIGGER trg_taller_au_fec_fin AFTER
UPDATE OF fec_fin ON t_taller
FOR EACH ROW
BEGIN
FOR i in 1 .. :new.dias.count
LOOP
dbms_output.put_line(:new.dias(i).dia || ' ' || :new.dias(i).hora);
END LOOP;
END;
/
By the way, as #XING mentioned, there's no use of dbms_output in a Trigger. You should rather consider logging them into a table.
Demo
As mentioned in my comments Triggers are used for handling of events on the table to . Although your posted script does not make much sense to me , however am giving the solution.
Also you simply cannot Select records from a Nested table. You need to use table operator to do so. Also in a trigger if you do Select on same table, you might land up is mutating, trigger/function. See below demo:
CREATE OR REPLACE TYPE dia_t AS OBJECT (
dia VARCHAR2(9),
hora VARCHAR2(6)
);
CREATE OR REPLACE TYPE DIA_TAB IS TABLE OF DIA_T;
CREATE TABLE t_taller (
pk NUMBER ,
dias dia_tab,
fec_ini DATE NOT NULL,
fec_fin DATE NOT NULL,
CONSTRAINT cst_primkey_taller PRIMARY KEY ( pk )
)
NESTED TABLE dias STORE AS dias_tab;
INSERT INTO t_taller VALUES (
1,
dia_tab(dia_t(
'A',
'B')
),
TO_DATE('10-Dec-2018','DD-Mon-YYYY'),
TO_DATE('10-Dec-2018','DD-Mon-YYYY')
);
CREATE OR REPLACE TRIGGER trg_taller_au_fec_fin
AFTER UPDATE OF fec_fin ON t_taller
FOR EACH ROW
BEGIN
FOR rec IN ( SELECT t.dia,
t.hora
FROM t_taller e,TABLE (e.dias ) t) --This is how you select records from nested table
LOOP
dbms_output.put_line(rec.dia || ' ' || rec.hora);
END LOOP;
END;
Output:
Error starting at line : 40 in command -
update t_taller set fec_fin = fec_fin + 20 where pk = 1
Error report -
ORA-04091: table SYSTM.T_TALLER is mutating, trigger/function may not see it
ORA-06512: at "SYSTM.TRG_TALLER_AU_FEC_FIN", line 2
ORA-04088: error during execution of trigger 'SYSTM.TRG_TALLER_AU_FEC_FIN'
So again you need to revisit your requirement.

Trigger, at line 1 ORA 04098

Hi im setting up some triggers and i cant get past this error
SET SERVEROUTPUT ON
CREATE OR REPLACE TRIGGER trigger2
BEFORE INSERT ON new_donation
FOR EACH ROW
--WHEN (new.contamt <= 10)
DECLARE
v_idno VARCHAR2(5);
v_driveno VARCHAR2(3);
v_contdate DATE;
v_contamt NUMBER(6,2);
BEGIN
SELECT IDNO, DRIVENO, CONTDATE, CONTAMT INTO v_idno, v_driveno, v_contdate, v_contamt
FROM OLD_DONATION2
WHERE IDNO = :new.idno;
IF :new.contamt < 50 THEN
RAISE_APPLICATION_ERROR (-20001, 'CONTRIBUTION TOO LOW FOR '
|| :new.idno || ' ' || :new.contamt);
END IF;
END;
/
SET SERVEROUTPUT OFF
another part
DECLARE
v_idno new_donation.idno%TYPE := '&in_idno';
v_driveno new_donation.driveno%TYPE := '&in_driveno';
v_contdate new_donation.contdate%TYPE := '&in_contdate';
v_contamt new_donation.contamt%TYPE := &in_contamt;
BEGIN
INSERT INTO new_donation
VALUES (v_idno, v_driveno, v_contdate, v_contamt);
END;
/
Im getting this error when I insert values at trigger2.
ERROR at line 1: ORA-04098: trigger 'XXXXXXXXX.NEW_DONATION' is
invalid and failed re-validation ORA-06512: at line 7
All im trying to do is insert some values input by the user to this new table, which is empty.
Also when donation amount is < 10 I want a error out.
Follow these steps.
Run this query select status from all_objects where object_name = 'TBL_USER_TRIGGER' and object_type = 'TRIGGER';
If the status is invalid run alter trigger trigger2 compile;
Then run show errors
It will throw the errors for you. You need to fix those.

How to query and execute trigger on the same table

Hope someone can help with this. I am new to triggers and I am trying to create a trigger that checks to see if the record being modified has a specific value.
example
I have a table called Filing that has a filing_id and a filing_status, I want to prevent someone from updating or deleting any records in that table has a filing_status="FILED".
so if i have the following
Filing_id Filing_status Val
--------- ------------- ---
0 Filed X
If someone tried to modify Val the trigger should stop it
I have created the following trigger:
CREATE or replace TRIGGER TRG_PREV_FILING
BEFORE DELETE or UPDATE
ON PF.FILING
FOR EACH ROW
declare
rowcnt number;
BEGIN
SELECT COUNT(filing_id) INTO rowcnt FROM PF.FILING
where status = 'FILED'
and filing_id = :new.filing_id;
if (rowcnt > 0)
then
raise_application_error (-20100, 'You can not delete Or Update initial record');
end if;
END;
The problem I am facing is I am getting:ORA-04091 which is "Table Filing is mutating, Trigger/function may not see it"
So basically I can't query on the same table that I am executing the trigger on? Is that the problem in my case and does anyone know a work around this?
I appreciate any help
You do not have to query the table trigger is firing on to be able to do that kind of check. You can get value of a column that is being modified using :old. Here is an example:
SQL> create table filing(
2 status varchar2(31),
3 val number
4 );
Table created
SQL> create or replace trigger TRG_FILING before delete or update on FILING
2 for each row
3 begin
4 if lower(:old.status) = 'filed'
5 then
6 raise_application_error(-20000, 'You cannot delete or modify this record');
7 end if;
8 end;
SQL> /
Trigger created
SQL> insert into FILING values('FILED', null);
1 row inserted
SQL> insert into filing values('OK', 1);
1 row inserted
SQL> commit;
Commit complete
SQL> select *
2 from filing;
STATUS VAL
------------------------------- ----------
FILED
OK 1
SQL> delete
2 from filing
3 where val is null;
ORA-20000: You cannot delete or modify this record
ORA-06512: at "HR.TRG_FILING", line 4
ORA-04088: error during execution of trigger 'HR.TRG_FILING'
The basic point is that you should design you database in a way that the trigger does its validation based on the updated/deleted row. If you have several rows with the same filing_id then you can overwork you database design. Maybe you really only check against the own table in which case you can use :old. But when you have several rows to check (which I assume because you make a count) then you have to use two tables. Here is a suggestion.
create table filing_status (filing_id number, status varchar2(10));
create table filing_content (filing_id number, content_id number, content varchar2(200));
CREATE or replace TRIGGER TRG_PREV_FILING
BEFORE DELETE or UPDATE
ON FILING_content
FOR EACH ROW
declare
rowcnt number;
BEGIN
SELECT COUNT(filing_id) INTO rowcnt FROM FILING_status
where status = 'FILED'
and filing_id = :new.filing_id;
if (rowcnt > 0)
then
raise_application_error (-20100, 'You can not delete Or update filed record');
end if;
END;
/
insert into filing_status values (1, 'FILING');
insert into filing_content values (1, 1, 'foo');
insert into filing_content values (1, 2, 'bar');
insert into filing_status values (1, 'FILED');
update filing_content set content = 'bahr' where filing_id = 1 and content_id = 2;
ERROR at line 1:
ORA-20100: You can not delete Or update filed record
ORA-06512: at "DEMO.TRG_PREV_FILING", line 9
ORA-04088: error during execution of trigger 'DEMO.TRG_PREV_FILING'

Resources