What is the proper way to create an update trigger with a variable getting value from the same table? - oracle

I'm creating a update trigger where an employee can never have a salary that is greater than the president's. However I need to subquery the president's salary for comparison and the "new" updated employee's salary.
I originally had the the subquery using from the from employees table but had to make a new table because of the mutating table problem. I don't think creating a new table is plausible solution for a real implementation.
Is there a way I can save the president's salary without creating a new table?
CREATE OR REPLACE TRIGGER prevent_salary
BEFORE UPDATE ON employees
FOR EACH ROW
declare pres_sal number(8,2);
BEGIN
select salary into pres_sal from employees_salary where job_id='AD_PRES';--employees_salary was employees but that gives mutating error
IF (:new.salary > pres_sal)
THEN UPDATE employees
SET salary = :old.salary
WHERE employee_id = :old.employee_id;
END IF;
END;

One way to do it is to save off the president's salary in a BEFORE STATEMENT trigger and then use that in the FOR EACH ROW trigger.
"Compound Triggers", which have been around at least since version 11.1, offer a nice way to do that all in one place.
Here is an example:
CREATE OR REPLACE TRIGGER prevent_salary
FOR UPDATE OF salary ON employees
COMPOUND TRIGGER
pres_sal NUMBER;
BEFORE STATEMENT IS
BEGIN
select salary
into pres_sal
from employees
where job_id='AD_PRES';
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
:new.salary := least(:new.salary, pres_sal);
END BEFORE EACH ROW;
END prevent_salary;

You may try this :
CREATE OR REPLACE TRIGGER prevent_salary
BEFORE UPDATE ON employees
FOR EACH ROW
declare pres_sal number(8,2);
BEGIN
select salary into pres_sal from employees_salary where job_id='AD_PRES';--employees_salary was employees but that gives mutating error
IF (:new.salary > pres_sal)
Raise_Application_Error (-20101, 'An employee''s salary couldn''t exceed president''s !');
END IF;
END;

Related

pl/sql-create trigger on a table -whenever a new record is inserted in the same table another column of the table should be updated

create trigger calculation after insert on employee
for each row
begin
if :new.updated_sal is null
then
update employee set updated_sal= (10/100)* salary
where id=:new.id;
end if;
end;
I would like to create a trigger on the employee table, whenever a new record is inserted in the same table, a 10% of salary in the salary column should be calculated and put into another column updated_sal.
If I try to insert a new record, it is showing that the table is mutated, etc
It's just the :new pseudorecord you need:
create trigger calculation
after insert on employee
for each row
begin
if :new.updated_sal is null then
:new.updated_sal := (10/100) * :new.salary;
end if;
end;
You need to use before insert trigger,
And use :New.updated_sal:= :new.salary * somevalue
To assign salary.

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

PL/SQL Triggers

I am using these tables:
flights (flno, origin, destination, distance, departs, arrives, price)
aircraft (aid, aname, crusingrange)
employees (eid, ename, salary)
certified (eid,aid)
and I need to create a trigger that displays a warning when inserting an employee with "666" anywhere in his/her name.
This is what I came up with so far; I am lost with the rest of it.
set serveroutput on
create or replace trigger emp_warning
before insert
on employees
for each row
declare
v_name;
begin
select e.ename into v_ename
from employees e
A trigger cannot "display a warning"; a trigger can raise an exception.
In the context of the body of a before insert for each row trigger, the value being supplied for the column is available from :NEW.columname
For example:
BEGIN
IF :NEW.ename LIKE '%666%' THEN
RAISE_APPLICATION_ERROR(-20000, 'ename contains ''666''.');
END IF;
END;
It's not mandatory that you use the RAISE_APPLICATION_ERROR. You could emit some line(s) using DBMS_OUTPUT.PUT_LINE... the line could include whatever text you wanted, including the word "warning". But this isn't really a display of a warning.
Use a check constraint instead of a trigger:
alter table empoloyees modify ename check (ename not like '%666%');
I finally figured it out thanks for you help. This my answer to the trigger question.
set serveroutput on
create or replace trigger name_warning
before insert on employees
for each row
begin
if :new.ename like '%666%' then
dbms_output.put_line('Warning employees name contains 666');
end if;
end;
/

Package for Insert/Update old salary value in emp_sal_history table whenever salary get changed without trigger

This is my Function
create or replace
function sal_incr
(
p_grade number)
return number
is
v_inc number;
begin
select raise_percent into v_inc from sal_inc where grade_id = p_grade;
return 1 + (v_inc/100);
end;
This is my procedure:
create or replace
procedure sal_increm
is
begin
UPDATE emp_task SET sal = sal * sal_incr(grade_id);
end;
how to do that package.. without using triggers how to update "old sal","modified by" and "modified on" in separate table
You can have multiple DML statements in a procedure; they'd be rather less useful if you couldn't. You can do a single insert into your history table based on the data in the task table, adding the executing user with the USER function and and the current time with SYSDATE.
create or replace
procedure sal_increm
is
begin
insert into emp_sal_history (empno, old_sal, modified_by, modified_on)
select empno, sal, user, sysdate
from emp_task;
update emp_task set sal = sal * sal_incr(grade_id);
end;
/
If you want to record the new salary as well you can calculate that in the insert too.
This will record a history record even for employees whose grade doesn't get an increment. If you have those or want to handle that possibility and exclude them, you can add
where sal_incr(grade_id) != 1
You could add that to the update as well.

trigger after insert on table

create or replace
trigger addpagamento
after insert on marcacoes_refeicoes
for each row
declare
nmarcacaoa number;
ncartaoa number;
begin
select nmarcacao into nmarcacaoa from marcacoes_refeicoes where rownum < (select count(*) from marcacoes_refeicoes);
select ncartao into ncartaoa from marcacoes_refeicoes where rownum < (select count(*) from marcacoes_refeicoes);
insert_pagamentos(nmarcacaoa, ncartaoa); --this is a procedure
exception when others then
raise_application_error(-20001, 'Error in Trigger!!!');
end addpagamento;
when i try to run the insert statement to the table "marcacoes_refeicoes" this procedure gives error: like the table is mutating
create or replace
procedure insert_pagamentos
(nmarcacaoa in number, ncartaoa in number)
AS
BEGIN
insert into pagamentos (nmarcacao, datapagamento, ncartao) values (nmarcacaoa, sysdate, ncartaoa);
commit;
END INSERT_PAGAMENTOS;
Short (oversimplified) answer:
You can't modify a table in a trigger that changes the table.
Long answer:
http://www.oracle-base.com/articles/9i/mutating-table-exceptions.php has a more in-depth explanation, including suggestions how to work around the problem.
You're hitting the mutating-table problem because you're selecting from the same table the trigger is on, but what you seem to be trying to do doesn't make sense. Your queries to get a value for nmarcacaoa and ncartaoa will return a no_data_found or too_many_rows error unless the table had exactly 2 rows in it before your insert:
select nmarcacao from marcacoes_refeicoes
where rownum < (select count(*) from marcacoes_refeicoes);
will return all rows except one; and the one that's excluded will be kind of random as you have no ordering. Though the state is undetermined within the trigger, so you can't really how many rows there are, and it won't let you do this query anyway. You won't normally be able to get a single value, anyway, and it's not obvious which value you actually want.
I can only imagine that you're trying to use the values from the row you are currently inserting, and putting them in your separate payments (pagamentos) table. If so there is a built-in mechanism to do that using correlation names, which lets you refer to the newly inserted row as :new (by default):
create or replace
trigger addpagamento
after insert on marcacoes_refeicoes
for each row
begin
insert_pagamentos(:new.nmarcacaoa, :new.ncartaoa);
end addpagamento;
The :new is referring to the current row, so :new.nmarcacaoa is the nmarcacaoa being inserted. You don't need to (and can't) get that value from the table itself. (Even with the suggested autonomous pragma, that would be a separate transaction and would not be able to see your newly inserted and uncommitted data).
Or you can just do the insert directly; not sure what the procedure is adding here really:
create or replace
trigger addpagamento
after insert on marcacoes_refeicoes
for each row
begin
insert into pagamentos(nmarcacao, datapagamento, ncartao)
values (:new.nmarcacaoa, sysdate, :new.ncartaoa);
end addpagamento;
I've removed the exception handler as all it was doing was masking the real error, which is rather unhelpful.
Editing this answer in view of the comments below:
You can use PRAGMA AUTONOMOUS_TRANSACTION to get rid of the error, but DONOT USE it as it will NOT solve any purpose.
Use PRAGMA AUTONOMOUS_TRANSACTION:
create or replace
trigger addpagamento
after insert on marcacoes_refeicoes
for each row
declare
PRAGMA AUTONOMOUS_TRANSACTION;
nmarcacaoa number;
ncartaoa number;
begin
select nmarcacao into nmarcacaoa from marcacoes_refeicoes where rownum < (select count(*) from marcacoes_refeicoes);
select ncartao into ncartaoa from marcacoes_refeicoes where rownum < (select count(*) from marcacoes_refeicoes);
insert_pagamentos(nmarcacaoa, ncartaoa); --this is a procedure
exception when others then
raise_application_error(-20001, 'Error in Trigger!!!');
end addpagamento;
However in this case , select count(*) from marcacoes_refeicoes will give you the new count after the current insertion into the table.

Resources