Using trigger to update a value - sql

How do I write a simple trigger to check when salary is updated it is not over 5% for an existing faculty member.
I have a block of code here.
create or replace TRIGGER TRG_Not_over_5per
BEFORE UPDATE OF F_SALARY ON FACULTY
DECLARE
sal FACULTY.F_SALARY%TYPE;
BEGIN
SELECT FACULTY.F_SALARY INTO sal FROM FACULTY
-- how do I use the WHERE CLAUSE here so that I get the salary affected
-- do some math on the first and second salary
-- if less than 5%, keep update, if not, reject update.
END;
Thanks in advance.

You don't need to use a SELECT in the trigger (*). Define the trigger as FOR EACH ROW and you can have access to the old and new values of any column belonging to the table.
create or replace TRIGGER TRG_Not_over_5per
BEFORE UPDATE OF F_SALARY ON FACULTY
FOR EACH ROW
So your code would look like:
if :new.f_salary < :old.f_salary * 1.05 then
raise_application_error (
-20000
, 'salary increase must be at least 5%'
);
end if;
This way of handling the rule violation is just a suggestion. You do whatever you need to. You don't need to handle the ELSE branch: Oracle will apply the update by default.
(*) In fact Oracle will hurl a mutating table exception if you do try to execute the query you want to write. Find out more.

Related

How to write a trigger that checks an updated value in Oracle?

For an assignment I need to write an Oracle SQL trigger that prevents the StdBalance column in the STUDENT table from exceeding 500. Normally I would just use a check constraint to enforce this however I am being forced to write a trigger for it. As expected Oracle is throwing a mutating error due to me using SELECT in an update trigger and will not let me update the StdBalance value with anything. Does anyone have any idea how I could work around this? Here is the code for the trigger.
CREATE OR REPLACE TRIGGER Balance_Trigger
BEFORE UPDATE ON STUDENT
FOR EACH ROW
DECLARE
Current_Balance NUMBER;
BEGIN
SELECT :new.StdBalance
INTO Current_Balance
FROM STUDENT
WHERE :new.stdID = StdID;
IF Current_Balance > 500
THEN Raise_Application_error(-20007, 'You cannot exceed an unpaid balance of $500');
end if;
end;
/
show error;
Just use :
Current_Balance := :new.StdBalance;
Instead of
SELECT :new.StdBalance
INTO Current_Balance
FROM STUDENT
WHERE :new.stdID = StdID;
to suppress the mutating error.
P.S. Even if such assignments are used in these cases, as David Faber warned, there's no need to return a value for Current_Balance, :new.StdBalance may be used directly in comparison for IF :new.StdBalance > 500 ....

In PL/SQL i got the following error in triggers

I'm getting the following errors
Errors for TRIGGER TRIG:
LINE/COL ERROR
-------- -----------------------------------------------------------------
11/1 PL/SQL: SQL Statement ignored
11/37 PL/SQL: ORA-00911: invalid character
Actual Question is:
Consider the following relation schemas
Emp1
empid name salary dno
Del_History
dno Rows_deleted Date1
Write a PL/SQL block to delete records of all employees who belong to a particular department and then record the dno, no of rows deleted and date on which deletion occurred in the Del_History table.
create or replace trigger trig after delete on emp1 for each row
declare
d number:=&d;
begin
if deleting then
update Del_History set dno=d;
update Del_History set date1=sysdate;
update Del_History set Rows_deleted=%rowcount;
end if;
delete from emp1 where dno=d;
end;
This may not be answering your question directly but some issues with the trigger as posted are:
Your trigger will execute after delete on the table for each row. There is no need to include a delete statement in your trigger. The delete has already happened.
To access column values of the deleted row use :old.column.
Since this is a row level trigger the value of sql%rowcount will always be 1.
If deleting is not necessary since the trigger is only an after delete trigger.
create or replace trigger trig
after delete on emp1
for each row
declare
begin
update del_history
set dno = :old.dno;
update del_history
set date1 = sysdate;
update del_history
set rows_deleted = sql%rowcount; -- always 1
end;
I don't see a need for a trigger as the actual question is "Write a PL/SQL block". Below you'll find an anonymous PL/SQL block:
A PL/SQL block is defined by the keywords DECLARE, BEGIN, EXCEPTION, and END. These keywords divide the block into a declarative part, an executable part, and an exception-handling part. Only the executable part is required.
that fullfills all the requirements.
-- Write a PL/SQL block ...
declare
v_department_number constant number := 42;
v_rows_deleted number;
begin
-- ... to delete records of all employees who belong to
-- a particular department ...
delete from emp1 where dno = v_department_number;
-- record number of deleted rows
v_rows_deleted := sql%rowcount;
-- ... and then record the dno, no of rows deleted and date on
-- which deletion occurred in the Del_History table.
insert into del_history (
dno
,rows_deleted
,date1
) values (
v_department_number
,v_rows_deleted
,sysdate
);
end;
/

TRIGGERS using HR database

I want to create a trigger “Salary_Not_Decrease” to ensure that the salary of an employee is not decreased during update of table Employee.
Please advise on how to start with creating such trigger.
I wrote the following code but not sure about it
CREATE OR REPLACE TRIGGER Salary_Not_Decrease
BEFORE INSERT OR UPDATE
ON employees
FOR EACH ROW
BEGIN
if :new.salary < :old.salary then
RAISE_APPLICATION_ERROR(-20001,'Salary should not be decreased ') ;
end if;
END;
Please advise
Your question indicates you want to prevent reductions in salary. You can't prevent a reduction in salary when there is no salary to reduce, so having this as a before insert trigger doesn't make sense. Make it just a before update trigger and you should be good to go also as pointed out in the comments you should check for nulls:
CREATE OR REPLACE TRIGGER Salary_Not_Decrease
BEFORE UPDATE
ON employees
FOR EACH ROW
BEGIN
if coalesce(:new.salary,:old.salary-1,0) < nvl(:old.salary,0) then
RAISE_APPLICATION_ERROR(-20001,'Salary should not be decreased ') ;
end if;
END;
Still reading the Documentation on triggers is a good place to learn more.

PL/SQL Triggers Library Infotainment System

I am trying to make a Library Infotainment System using PL/SQL. Before any of you speculate, yes it is a homework assignment but I've tried hard and asking a question here only after trying hard enough.
Basically, I have few tables, two of which are:
Issue(Bookid, borrowerid, issuedate, returndate) and
Borrower(borrowerid, name, status).
The status in Borrower table can be either 'student' or 'faculty'. I have to implement a restriction using trigger, that per student, I can issue only 2 books at any point of time and per faculty, 3 books at any point of time.
I am totally new to PL/SQL. It might be easy, and I have an idea of how to do it. This is the best I could do. Please help me in finding design/compiler errors.
CREATE OR REPLACE TRIGGER trg_maxbooks
AFTER INSERT ON ISSUE
FOR EACH ROW
DECLARE
BORROWERCOUNT INTEGER;
SORF VARCHAR2(20);
BEGIN
SELECT COUNT(*) INTO BORROWERCOUNT
FROM ISSUE
WHERE BORROWER_ID = :NEW.BORROWER_ID;
SELECT STATUS INTO SORF
FROM BORROWER
WHERE BORROWER_ID = :NEW.BORROWER_ID;
IF ((BORROWERCOUNT=2 AND SORF='STUDENT')
OR (BORROWERCOUNT=3 AND SORF='FACULTY')) THEN
ROLLBACK TRANSACTION;
END IF;
END;
Try something like this:
CREATE OR REPLACE TRIGGER TRG_MAXBOOKS
BEFORE INSERT
ON ISSUE
FOR EACH ROW
BEGIN
IF ( :NEW.BORROWERCOUNT > 2
AND :NEW.SORF = 'STUDENT' )
OR ( :NEW.BORROWERCOUNT > 3
AND :NEW.SORF = 'FACULTY' )
THEN
RAISE_APPLICATION_ERROR (
-20001,
'Cannot issue beyond the limit, retry as per the limit' );
END IF;
END;
/
There should not be a commit or rollback inside a trigger. The logical exception is equivalent to ROLLBACK
This is so ugly I can't believe you're being asked to do something like this. Triggers are one of the worst ways to implement business logic. They will often fail utterly when confronted with more than one user. They are also hard to debug because they have hard-to-anticipate side effects.
In your example for instance what happens if two people insert at the same time? (hint: they won't see the each other's modification until they both commit, nice way to generate corrupt data :)
Furthermore, as you are probably aware, you can't reference other rows of a table inside a row level trigger (this will raise a mutating error).
That being said, in your case you could use an extra column in Borrower to record the number of books being borrowed. You'll have to make sure that the trigger correctly updates this value. This will also take care of the multi-user problem since as you know only one session can update a single row at the same time. So only one person could update a borrower's count at the same time.
This should help you with the insert trigger (you'll also need a delete trigger and to be on the safe side an update trigger in case someone updates Issue.borrowerid):
CREATE OR REPLACE TRIGGER issue_borrower_trg
AFTER INSERT ON issue
FOR EACH ROW
DECLARE
l_total_borrowed NUMBER;
l_status borrower.status%type;
BEGIN
SELECT nvl(total_borrowed, 0) + 1, status
INTO l_total_borrowed, l_status
FROM borrower
WHERE borrower_id = :new.borrower_id
FOR UPDATE;
-- business rule
IF l_status = 'student' and l_total_borrowed >= 3
/* OR faculty */ THEN
raise_application_error(-20001, 'limit reached!');
END IF;
UPDATE borrower
SET total_borrowed = l_total_borrowed
WHERE borrower_id = :new.borrower_id;
END;
Update: the above approach won't even work in your case because you record the issue date/return date in the issue table so the number of books borrowed is not a constant over time. In that case I would go with a table-level POST-DML trigger. After each DML verify that every row in the table validates your business rules (it won't scale nicely though, for a solution that scales, see this post by Tom Kyte).

Oracle SQL Trigger

I want to prevent the database from storing any values bigger than 20 into a table.
CREATE OR REPLACE TRIGGER Dont_Allow
AFTER INSERT ON Cities
FOR EACH ROW
WHEN (new.IDCity > 20)
BEGIN
dbms_output.put_line(' Failed to insert ' || :new.IDCity);
delete from orase where IDCity=:new.IDCity;
END;
While this does work in terms of not actually adding anything with an ID > 20, every time the trigger tries to do its magic, this shows up:
ORA-04091: table SYSTEM.ORASE is mutating, trigger/function may not see it
ORA-06512: at "SYSTEM.DONT_ALLOW", line 6
ORA-04088: error during execution of trigger 'SYSTEM.DONT_ALLOW'
What's a proper way of doing what I want?
EDIT:
I've decided to use a trigger for this:
After a new row is inserted into Employees, a trigger checks the new guy's salary and if it's above 21 units / hour, it takes 5% off management's bonus. Lame, but hey - I'm using a trigger to solve a problem I don't have: the outcome won't be pretty.
CREATE OR REPLACE TRIGGER Bite_Bonus
AFTER INSERT ON Employees
FOR EACH ROW
WHEN (new.HourSalary > 20)
BEGIN
update Management set Bonus = Bonus - 5/100 * Bonus;
END;
You shouldn't be using a TRIGGER for that, you should be using a CHECK, like CONSTRAINT city_id_below_20 CHECK (IDCity < 20). You can use ALTER TABLE ADD CONSTRAINT to put it on an existing table.
As TC1 indicated, the proper way to enforce this sort of requirement is to use a constraint.
If you are forced to use the inferior approach because this is a school assignment, you most likely want to raise an exception in your trigger
CREATE OR REPLACE TRIGGER Dont_Allow
BEFORE INSERT OR UPDATE ON Cities
FOR EACH ROW
WHEN (new.IDCity > 20)
BEGIN
RAISE_APPLICATION_ERROR( -20001, 'IDCity cannot exceed 20 so rejecting invalid value: ' || :new.IDCity );
END;
If you need to use a trigger for this, make it a BEFORE INSERT trigger, not an AFTER INSERT - you don't want that insert to happen at all. Trying to "undo" it after the fact is not a good approach.
To abort the insert, all you need to do is raise an exception within that trigger. Probably the best thing for this is to raise an application error.