How to call procedure using triggers - oracle

Table:
create table department(deptno number, deptname varchar2(50), deptloc varchar2(50));
insert into department values(1,'A','X');
insert into department values(2,'B','Y');
insert into department values(3,'C','Z');
Stored procedure :
create or replace procedure secure_dml(i_month IN varchar2)
is
begin
if i_month <> 'March' then
dbms_output.put_line('You can modify or add a department only at the end of a financial year');
else
--should I write insert/update DML statement?
end;
Trigger :
create or replace trigger tr_check_dept before insert on department
begin
dbms_output.put_line('Record inserted');
end;
Requirement :
Implement the following business rule with the help of a Procedure and a Trigger :-
i. Changes to the data in the Department table, will be allowed only in the month of March.
ii. Create a procedure named SECURE_DML that prevents the DML statement from executing in any other month other than March. In case, if a user tries to modify the table in any other month apart from March, the procedure should display a message
“You can modify or add a department only at the end of a financial year”
iii. Create a statement level trigger named TR_CHECK_DEPT on the Department table that calls the above procedure.
iv. Test it by inserting a new record in the Department table

Basically, you could do everything within a trigger, but OK - that's some kind of a homework. Here's how I understood it.
Procedure doesn't do anything "smart", just displays the message. Note that DBMS_OUTPUT.PUT_LINE call displays a message, it doesn't prevent anyone to do anything - instead of it, you should raise_application_error.
The answer to your question
should I write insert/update DML statement?
is - in my opinion - NO, you shouldn't.
Trigger calls that procedure. Yours doesn't check the month, while it should (i.e. move that control from procedure to trigger).
Everything put together might look like this:
SQL> create or replace procedure secure_dml
2 is
3 begin
4 raise_application_error(-20000,
5 'You can modify or add a department only at the end of a financial year');
6 end;
7 /
Procedure created.
SQL> create or replace trigger tr_check_dept
2 before insert on department
3 for each row
4 begin
5 if extract(month from sysdate) <> 3 then
6 secure_dml;
7 end if;
8 end;
9 /
Trigger created.
SQL> insert into department(deptno, deptname, deptloc)
2 values (4, 'D', 'W');
insert into department(deptno, deptname, deptloc)
*
ERROR at line 1:
ORA-20000: You can modify or add a department only at the end of a financial year
ORA-06512: at "SCOTT.SECURE_DML", line 4
ORA-06512: at "SCOTT.TR_CHECK_DEPT", line 3
ORA-04088: error during execution of trigger 'SCOTT.TR_CHECK_DEPT'
SQL>
Just for testing purposes, as it is September today, let's modify trigger code so that it works for this month (instead of March) and see what INSERT does in that case.
SQL> create or replace trigger tr_check_dept
2 before insert on department
3 for each row
4 begin
5 if extract(month from sysdate) <> 9 then --> this line was changed
6 secure_dml;
7 end if;
8 end;
9 /
Trigger created.
SQL> insert into department(deptno, deptname, deptloc)
2 values (4, 'D', 'W');
1 row created.
SQL>
Right; now it works.

Related

Create trigger that does the same as unique static constraint PL/SQL

As a disclaimer, this question is only for my curiosity and for practicing triggers and compound triggers particularly.
I've been trying to replace the UNIQUE constraint with a trigger in order to understand triggers more, but I haven't been successful so far, mainly because of the global variables that I'm not so comfortable with inside the compound triggers.
what I'm trying to do with a trigger :
ALTER TABLE Employee
ADD CONSTRAINT emp_UQ
UNIQUE (id_emp, id_office);
here's what I tried so far (t for type, g for global):
CREATE OR REPLACE TRIGGER BIUUniqueConstraint
FOR INSERT OR UPDATE ON Employee
COMPOUND TRIGGER
TYPE tIdEmpOffice IS TABLE OF Employee.id_emp%TYPE
INDEX BY VARCHAR2(80);
gIdEmpOffice tIdEmpOffice;
TYPE tId_emp IS TABLE OF Employee.id_emp%TYPE;
gId_emp tId_emp;
TYPE tId_office IS TABLE OF Employee.id_office%TYPE;
gId_office tId_office;
BEFORE STATEMENT IS
BEGIN
SELECT e.id_emp, e.id_office
BULK COLLECT INTO gId_emp, gId_office
FROM Employee e
ORDER BY e.id_emp;
FOR j IN 1..gId_emp.COUNT() LOOP
gIdEmpOffice(gId_emp(j)) := gId_office(j);
END LOOP;
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
IF INSERTING THEN
DBMS_OUTPUT.PUT_LINE(gIdEmpOffice(:NEW.id_emp);
END IF;
END BEFORE EACH ROW;
END BIUCompteParti;
/
I have no clue how to move forward with this trigger and would like suggestions and explanations if possible about how to use globale variables to store data and how to use them on a row level.
Here's one option.
Sample table:
SQL> create table employee (id_emp number, id_office number);
Table created.
Trigger:
SQL> create or replace trigger trg_emp_unique
2 for insert or update on employee
3 compound trigger
4 type t_row is record (id_emp number, id_office number);
5 type t_tab is table of t_row;
6 l_tab t_tab := t_tab();
7 l_cnt number;
8
9 before each row is
10 begin
11 l_tab.extend;
12 l_tab(l_tab.last).id_emp := :new.id_emp;
13 l_tab(l_tab.last).id_office := :new.id_office;
14 end before each row;
15
16 after statement is
17 begin
18 for i in l_tab.first .. l_tab.last loop
19 select count(*) into l_cnt
20 from employee
21 where id_emp = l_tab(i).id_emp
22 and id_office = l_tab(i).id_office;
23 if l_cnt > 1 then
24 raise_application_error(-20000, 'Unique constraint violated');
25 end if;
26 end loop;
27 l_tab.delete;
28 end after statement;
29 end trg_emp_unique;
30 /
Trigger created.
SQL>
As you can see, it doesn't contain before statement nor after each row parts; if they aren't used, you don't have to put them into the trigger.
Let's try it:
SQL> insert into employee(id_emp, id_office) values (1, 1);
1 row created.
SQL> insert into employee(id_emp, id_office) values (1, 2);
1 row created.
Re-insert the first combination (1, 1):
SQL> insert into employee(id_emp, id_office) values (1, 1);
insert into employee(id_emp, id_office) values (1, 1)
*
ERROR at line 1:
ORA-20000: Unique constraint violated
ORA-06512: at "SCOTT.TRG_EMP_UNIQUE", line 22
ORA-04088: error during execution of trigger 'SCOTT.TRG_EMP_UNIQUE'
That failed (as it should). Let's update existing rows:
SQL> select * from employee;
ID_EMP ID_OFFICE
---------- ----------
1 1
1 2
SQL> update employee set id_office = 5;
update employee set id_office = 5
*
ERROR at line 1:
ORA-20000: Unique constraint violated
ORA-06512: at "SCOTT.TRG_EMP_UNIQUE", line 22
ORA-04088: error during execution of trigger 'SCOTT.TRG_EMP_UNIQUE'
Kind of works.
You can count whether a paired value for those columns already exists with a statement level trigger such as
CREATE OR REPLACE TRIGGER trg_chk_unique_emp_dept_id
AFTER INSERT ON employee
DECLARE
val INT;
BEGIN
SELECT NVL(MAX(COUNT(*)),0)
INTO val
FROM employee
GROUP BY id_emp, id_office;
IF val > 1 THEN
RAISE_APPLICATION_ERROR(-20304,
'Each employee may be assigned to a department once at most !');
END IF;
END;
/
which will check if an attempt made to insert the same value pairs more than once, and if so, it will hurl with an error message.
Demo

PLSQL trigger not working 0 rows inserted

Whatever I do to the trigger always returns "0 rows inserted".
It is like it can't find the new values after inserting them.
After adding the exception it return no_data_found and i don't know why.
before insert or update of rent_date, return_date on rent
for each row
declare
pragma AUTONOMOUS_TRANSACTION;
v_rentDate date;
v_returnDate date;
begin
select rent_date
into v_rentDate
from rent
where rent_date = :new.rent_date;
select return_date
into v_returnDate
from rent
where return_date = :new.return_date;
if v_returnDate < v_rentDate then
raise_application_error(-20158, 'Return date must be after the rent date');
else
dbms_output.put_line('TEST');
end if;
exception when no_data_found then raise_application_error(-20157, 'No data found');
end;
/
insert into rent values (82,sysdate-5,101,sysdate,sysdate+5,100);
--0 rows inserted
It appears that you're doing it the wrong way. Here's why:
you are trying to select values from a table you're currently inserting into (or updating existing values)
Oracle complains that it can't do that because the table is mutating
in order to "fix" it, you used pragma autonomous_transaction which isolates trigger code from the main transaction
You shouldn't use that pragma for such a purpose. Lucky you, trigger can be rewritten in a simpler manner, the one that doesn't cause the mutating table error. As you want to compare rent_date and return_date, do it directly. Here's an example (see line #5):
SQL> create table rent
2 (id number,
3 rent_date date,
4 return_date date
5 );
Table created.
SQL> create or replace trigger trg_biu_rent
2 before insert or update on rent
3 for each row
4 begin
5 if :new.return_date < :new.rent_date then
6 raise_application_error (-20158, 'Return date must be after the rent date');
7 end if;
8 end;
9 /
Trigger created.
Testing:
SQL> -- This will fail
SQL> insert into rent (id, rent_date, return_date) values
2 (1, date '2019-05-25', date '2019-04-10');
insert into rent (id, rent_date, return_date) values
*
ERROR at line 1:
ORA-20158: Return date must be after the rent date
ORA-06512: at "SCOTT.TRG_BIU_RENT", line 3
ORA-04088: error during execution of trigger 'SCOTT.TRG_BIU_RENT'
SQL> -- This is OK
SQL> insert into rent (id, rent_date, return_date) values
2 (1, date '2019-03-28', date '2019-10-20');
1 row created.
SQL>

Statement Trigger PL/SQL

I need to create a statement trigger that allows data to be updated in PurchaseStock table only during office hours.
The PurchaseStock table is :
PurchaseStock (StockID, ProductID, QuantityIn, Date)
Note that productId is a foreign key here.
I know how to create a trigger for an update but how do I cater for the hours?
Any help is appreciated.
Here, where I live, off-hours are also Saturday & Sunday (i.e. weekend), so - I'd suggest something like this:
SQL> create table purchase_stock (stock_id number, quantity number);
Table created.
SQL> create or replace trigger trg_biu_pursto
2 before insert or update on purchase_stock
3 declare
4 -- day number (1 = Monday, 2 = Tuesday, ..., 7 = Sunday)
5 l_day number := to_number(to_char(sysdate, 'd'));
6 -- current hour (e.g. now is 13:45 -> l_hour = 13)
7 l_hour number := to_number(to_char(sysdate, 'hh24'));
8 begin
9 if l_day in (6, 7) or
10 l_hour not between 9 and 17
11 then
12 raise_application_error(-20000, 'Non-working time; table not available');
13 end if;
14 end;
15 /
Trigger created.
SQL> insert into purchase_stock values (1, 2);
insert into purchase_stock values (1, 2)
*
ERROR at line 1:
ORA-20000: Non-working time; table not available
ORA-06512: at "SCOTT.TRG_BIU_PURSTO", line 10
ORA-04088: error during execution of trigger 'SCOTT.TRG_BIU_PURSTO'
SQL>
Assuming your office hours between 08:00 and 17:00, you can create such a trigger below, to prevent updating data out of office hour you can use raise_application_error statement :
CREATE OR REPLACE TRIGGER trg_upd_PrcStock
BEFORE UPDATE ON PurchaseStock
FOR EACH ROW
DECLARE
v_hour pls_integer;
BEGIN
select into v_hour extract(hour from cast(sysdate as timestamp)) from dual;
IF v_hour between 8 and 16 THEN
-- trigger code
ELSE
raise_application_error('-20333','You can not perform update out of office hours!');
END IF;
END;

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'

Simple oracle triggers

Simple one. I´m a bit of a newvbie with PLSql and oracle's error messages are never too helpful.
I want to do a simple trigger to update a column with the current date i.e. 'modified date' column of a table. Getting an odd error though.
The idea is simple
create table test1 (tcol varchar2(255), tcol2 varchar2(255))
CREATE OR REPLACE TRIGGER testTRG
AFTER INSERT OR UPDATE ON test1
FOR EACH ROW
BEGIN
update test1
set tcol2 = to_char(sysdate)
where tcol = :OLD.tcol;
END;
insert into test1 (tcol) values ('test1');
this pops up the error:
ORA-04091: table RAIDBIDAT_OWN.TEST1 is mutating, trigger/function may not see it
ORA-06512: at "RAIDBIDAT_OWN.TESTTRG", line 2
ORA-04088: error during execution of trigger 'RAIDBIDAT_OWN.TESTTRG'
Would anyone have a quick solution for this?
cheers,
f.
Your situation:
SQL> create table test1 (tcol varchar2(255), tcol2 varchar2(255))
2 /
Table created.
SQL> CREATE OR REPLACE TRIGGER testTRG
2 AFTER INSERT OR UPDATE ON test1
3 FOR EACH ROW
4 BEGIN
5 -- Your original trigger
6 update test1
7 set tcol2 = to_char(sysdate)
8 where tcol = :OLD.tcol;
9 END;
10 /
Trigger created.
SQL> insert into test1 (tcol) values ('test1');
insert into test1 (tcol) values ('test1')
*
ERROR at line 1:
ORA-04091: table [schema].TEST1 is mutating, trigger/function may not see it
ORA-06512: at "[schema].TESTTRG", line 3
ORA-04088: error during execution of trigger '[schema].TESTTRG'
Tony's suggestion is almost right, but unfortunately it doesn't compile:
SQL> CREATE OR REPLACE TRIGGER testTRG
2 AFTER INSERT OR UPDATE ON test1
3 FOR EACH ROW
4 BEGIN
5 -- Tony's suggestion
6 :new.tcol2 := sysdate;
7 END;
8 /
CREATE OR REPLACE TRIGGER testTRG
*
ERROR at line 1:
ORA-04084: cannot change NEW values for this trigger type
Because you can only change NEW values in before-each-row triggers:
SQL> create or replace trigger testtrg
2 before insert or update on test1
3 for each row
4 begin
5 :new.tcol2 := sysdate;
6 end;
7 /
Trigger created.
SQL> insert into test1 (tcol) values ('test1');
1 row created.
SQL> select * from test1
2 /
TCOL
------------------------------------------------------------------------------------------
TCOL2
------------------------------------------------------------------------------------------
test1
13-09-2010 12:37:24
1 row selected.
Regards,
Rob.
The trigger should simply read:
CREATE OR REPLACE TRIGGER testTRG
BEFORE INSERT OR UPDATE ON test1
FOR EACH ROW
BEGIN
:new.tcol2 := to_char(sysdate);
END;
There is no requirement to issue another update of the same row (and as you have found, you cannot).
It is more usual to use DATE columns to store dates:
create table test1 (tcol varchar2(255), tcol2 date);
CREATE OR REPLACE TRIGGER testTRG
BEFORE INSERT OR UPDATE ON test1
FOR EACH ROW
BEGIN
:new.tcol2 := sysdate;
END;

Resources