PL/SQL Trigger to sum rows and update other rows - sql

I tried to do the following:
Write a trigger such that the budget of a department is the sum of salaries paid to employees in the department (remember employees work only a percent of time in a department). The DEPT table includes Dept ID (Did), Budget, ManagerID, the Employee(EMP) table includes EMPID(eid), SALARY, BONUS, AGE, and the WORKS table includes eid, Did, working_time.
The code I wrote was:
CREATE TRIGGER BUDGET_CHK
BEFORE
INSERT OR UPDATE OF BUDGET OR DELETE ON DEPT
FOR EACH ROW
BEGIN
UPDATE DEPT
SET BUDGET =
(SELECT SUM(E.SALARY)
FROM EMP E, WORKS W, DEPT D
WHERE E.eid=W.eid AND D.did=W.did
GROUP BY W.did)
END;
I am new to oracle. Can someone correct me on this? Thanks!
Errors (from comments below):
pl/sql: sql statement ignored
ORA-00933 sql command not properly ended
Error(16): PLS-00103: Encountered the symbol "end-of-file" when expecting
one of the following: ( begin case declare end exception exit for goto if
loop mod null pragma raise return select update while with <an identifier>
<a double-quoted delimited-identifier> <a bind variable> << continue close
current delete fetch lock insert open rollback savepoint set sql execute
commit forall merge pipe purge

You have several problems in one spot.
You missed ";" symbol after UPDATE statement - this is the technical error.
Your UPDATE statement changes ALL rows in DEPT table - I don't think this is that you expect.
Your subquery can return more than 1 row - this means you will get "ORA-01427: single-row subquery returns more than one row" runtime error
Probably you want something like:
UPDATE DEPT D
SET BUDGET =
(SELECT SUM(E.SALARY)
FROM EMP E, WORKS W
WHERE E.eid=W.eid
AND D.did=W.did)
WHERE EXISTS (SELECT *
FROM EMP E, WORKS W
WHERE E.eid=W.eid
AND D.did=W.did
);
You create row-level trigger on DEP table and try to change DEPT table inside the body - you should learn about "mutating table problem": http://asktom.oracle.com/pls/asktom/ASKTOM.download_file?p_file=6551198119097816936
In the multiuser environment when several transactions can change data in tables
your code will get irrelevant results due to transaction isolation. Please learn Oracle concepts: http://docs.oracle.com/cd/B28359_01/server.111/b28318/consist.htm

CREATE OR REPLACE TRIGGER dept_trigger before
INSERT OR UPDATE OF budget ON dept
FOR EACH row
declare
l_budget NUMBER;
BEGIN
SELECT SUM(sal) INTO l_budget
FROM employees e
INNER JOIN works w
ON w.eid =e.eid;
IF :new.budget < l_budget THEN
raise_application_error(-20001,'Out of budget');
END IF;
END;
From Your description it might work.

Related

there is few mistake in trigger help me to solve that problem

Create trigger that will
not allow update of salary for employee
who are in sales department.
Emp(no,name,dno,salary,designamtion)
Dept(dno,name,location)
Errors: TRIGGER MYTRIGGER1
Line/Col: 6/11 PLS-00049: bad bind variable 'OLD.EMP'
Line/Col: 6/31 PLS-00049: bad bind variable 'OLD.EMP'
DROP TABLE EMP;
DROP TABLE DEPT;
CREATE TABLE Dept
(
dno NUMBER PRIMARY KEY,
name VARCHAR(15),
location VARCHAR(15) );
CREATE TABLE Emp
(
no NUMBER PRIMARY KEY,
name VARCHAR(15),
dno NUMBER,
salary NUMBER,
designamtion VARCHAR(15),
FOREIGN KEY(dno) REFERENCES Dept(dno) );
insert into DEPT values (1,'SALES','GUJARAT');
insert into DEPT values (2,'MARKETING','UP');
insert into DEPT values (3,'MANUFACTURING','MP');
insert into DEPT values (4,'DEALING','VAPI');
insert into DEPT values (5,'SELL','TAPI');
insert into EMP values (1,'AMAN',2,45400,'MANAGER');
insert into EMP values (2,'BHAMAN',5,20000,'GM');
insert into EMP values (3,'CHAMAN',3,34400,'ADVISOR');
insert into EMP values (4,'DAMAN',4,75400,'WORKER');
insert into EMP values (5,'KHAMAN',1,42400,'MANAGER');
CREATE OR REPLACE trigger MYTRIGGER1
BEFORE UPDATE OF SALARY ON EMP
for each row
declare
hmmm VARCHAR(15);
begin
select Dept.name into hmmm
from Dept, Emp
where :old.emp.no=no and :old.emp.dno=Dept.dno;
dbms_output.put_line(hmmm);
end;
You don't need to alias the table the trigger is on. This will compile fine:
CREATE OR REPLACE trigger MYTRIGGER1
BEFORE UPDATE OF SALARY ON EMP
for each row
declare
hmmm VARCHAR(15);
begin
select Dept.name into hmmm
from Dept, Emp
where :old.no=no and :old.dno=Dept.dno;
dbms_output.put_line(hmmm);
end;
However, this trigger will fail when you update the table. You cannot select from the table itself in the trigger body. This will raise a mutating table error. Since it is an assignment I'll leave the research up to you.
As Koen said, your code will suffer from mutating table error.
Lucky you - you don't have to select from the table you're just updating (the emp table, right?) - you have everything you need in :new.dno. So:
SQL> create or replace trigger mytrigger1
2 before update of salary on emp
3 for each row
4 declare
5 hmmm varchar2(15);
6 begin
7 select d.name
8 into hmmm
9 from dept d
10 where d.dno = :new.dno;
11
12 dbms_output.put_line('Department name = ' || hmmm);
13 end;
14 /
Trigger created.
Testing:
SQL> set serveroutput on
SQL>
SQL> update emp set salary = 1000 where no = 1;
Department name = MARKETING
1 row updated.
SQL> update emp set salary = 1000;
Department name = MARKETING
Department name = SELL
Department name = MANUFACTURING
Department name = DEALING
Department name = SALES
5 rows updated.
SQL>
Your request is a trigger that disallows certain action for employees in Sales. Your trigger would not do so even it compiled. Two problems:
dbms_output does not prevent anything it merely 'prints' a message.
it prevents any update to salary, except when emp.dno or emp.no is also updated.
First (getting up on soapbox). You are enforcing a business rule. This should not be done in a trigger. It should be done in either the business rules engine of your application or in a database constraint or if you want to be super cautions both. Now a trigger will do the enforcement but business rule violations should glaringly obvious and easy to find, in a trigger becomes a side effect of an action, hidden away and difficult to find. (OK enough of that getting off soapbox).
The prevention method your looking for is raise_application_error. And you want a little code as possible in a trigger. In this case you have everything needed from the EMP table through the pesudo rows :old and :new so there is no reason to join your tables. However you do need to look at the specific department. It is too bad Oracle constrains the Exists predicate to introduction of a sub-select, this would be a perfect place for the structure:
If EXISTS (selects ...) then ...
But that is not available. The following uses a technique of reversing the requirement. That is it selects what is NOT wanted then if found it raise the exception, and if not found it suppress the Oracle raised error. So:
create or replace trigger mytrigger1
before update of salary on emp
for each row
declare
hmmm varchar2(01);
begin
select null
into hmmm
from dept d
where d.dno = :old.dno
and d.name = 'SALES';
raise_application_error(-20199, 'Can not update salary for Employee in Sales.');
exception
when no_data_found then null;
end mytrigger1;
This is sometimes referred to as "Programming the Exception" and is not a generally recommended procedure but at times it is useful. See fiddle here.
There is an outstanding question not addressed here. Can an employee in Sales have a salary change if they are also transferring departments. I.e. should the following succeed or fail?
update emp e
set dno = 2
, salary = salary + 5000
where e.no = 5;

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

Using oracle triggers to update a second table where a condition has been

I'm new to pl/sql and grappling with triggers. I am required to use a trigger for this code. I have 2 tables, job (job_id, job_name, job_price) and job_history (job_id, oldprice, datechanged). I'm trying to create a trigger that adds the old job details to the job_history table when the job_price field in the job table is updated either if no row already exist or if the new job price for that job id is more than any previously stored prices for that job id in the job_history table. The job id field in the job table cannot have duplicates but the job id field in the job_history table can have duplicates. Further, if the condition is not met, that is, the new job price is less than all previously stored prices for that job id, then the error should be trapped.
I've tried this code:
CREATE OR REPLACE TRIGGER conditional_update_job_hist
AFTER UPDATE OF jbsprice ON job
FOR EACH ROW
WHEN (new.jbsprice)<min(old.jbsprice);
BEGIN
INSERT INTO job_history (jbsid, oldprice) VALUES (:old.jbsid,:old.jbsprice);
IF :new.price is<>min(oldprice) THEN
RAISE_APPLICATION_ERROR('Condition not met.');
ENDIF;
END;
/
This resulted in an error at line 4
ORA-00920: invalid relational operator.
I've checked the oracle online documentation. It's confusing. Do I need to use a cursor and loop inside the trigger? The less than operator looks okay and the min(function) looks okay. I cannot see where I'm going wrong. Please help.
at a first glance, I would suggest the following:
CREATE OR REPLACE TRIGGER conditional_update_job_hist
AFTER UPDATE OF jbsprice ON job
FOR EACH ROW
DECLARE
hist_exists number;
BEGIN
hist_exists := 0;
begin
-- select 1 if there is an entry in job_history of that jbsid
-- and an oldprice exists which is more than new jbsprice
select distinct 1
into hist_exists
from job_history
where jbsid = :old.jbsid
and oldprice > :new.jbsprice;
exception when no_data_found then hist_exists := 0;
end;
IF hist_exists = 0 then
INSERT INTO job_history (jbsid, oldprice) VALUES (:old.jbsid,:old.jbsprice);
END IF;
END;
/
Ignoring :
CREATE OR REPLACE TRIGGER conditional_update_job_hist
AFTER UPDATE OF jbsprice ON job
FOR EACH ROW
WHEN (:new.jbsprice)<min(:old.jbsprice);
BEGIN
INSERT INTO job_history (jbsid, oldprice) VALUES (:old.jbsid,:old.jbsprice);
IF :new.price is<>min(oldprice) THEN
RAISE_APPLICATION_ERROR('Condition not met.');
ENDIF;
END;

Trigger in Oracle SQL: When Timesheet is approved it should update the Payroll Table

When the Table Timesheet is approved (Timesheet_approved* is Not Null) a trigger should fire which will calculate the payment for standard hours due (Payroll_standard*) and the payment due for any overtime (Payroll_overtime* = standard hours X 1.5) for the previous week on the table Payroll.
It should also calculate the pension contribution (Payroll_pension* = 10% of standard + Over time) due and then update the payroll table (working out what the next payroll id would be)
Please note * is used to point out the names of the table attributes.
Tables being used/ affected are Timesheet and Payroll
So far I have the code below. However, I keep getting errors around bad variable bind:
LINE/COL ERROR
-------- -------------------------------------------------------------
32/3 PL/SQL: SQL Statement ignored
33/3 PL/SQL: ORA-01747: invalid user.table.column, table.column, or
column specification
33/3 PLS-00049: bad bind variable 'NEW.PAYROLL_STANDARD'
34/3 PLS-00049: bad bind variable 'NEW.PAYROLL_OVERTIME'
35/3 PLS-00049: bad bind variable 'NEW.PAYROLL_PENSION'
SQL>
SQL:
CREATE OR REPLACE TRIGGER trg_PAYROLLCALC
After UPDATE
on Timesheet FOR EACH ROW
Declare
V_TimesheetHRS number (3);
V_GRADEHRS number (3);
V_TimesheetOT number (3);
v_OTGRADEHRS number (3);
v_payrollID number (3);
BEGIN
SELECT Grade_rate into V_GRADEHRS
FROM Grade join Employee on (Emp_grade = grade_id)
where emp_ID = Timesheet_emp
;
SELECT Timesheet_hours into V_TimesheetHRS
From Funtom_timesheet join Funtom_employee on (emp_ID = Timesheet_emp)
where emp_ID = Timesheet_emp
;
Select Timesheet_OT into V_TimesheetOT
From Timesheet join Employee on (emp_ID = Timesheet_emp)
where emp_ID = Timesheet_emp
;
select Sum(Grade_rate * 1.5) into v_OTGRADEHRS
from Grade join Employee on (Emp_grade = grade_id)
where emp_ID = Timesheet_emp
;
IF Timesheet_approved IS NOT NULL then
Update Payroll set
:new.Payroll_standard := V_GRADEHRS * V_TimesheetHRS;
:new.Payroll_overtime := v_OTGRADEHRS * V_TimesheetOT;
:new.Payroll_pension := ((V_GRADEHRS * V_TimesheetHRS)+(v_OTGRADEHRS * V_TimesheetOT));
END IF;
Select MAX(Payroll_id)+1 into v_payrollID
from Payroll;
:new.Payroll_id := v_payrollID;
END;
/
Notes:
Grade_rate is the standard rate at which the worker is paid,
Grade_id is the PK of that rate,
Emp_grade is the FK that maps on to Grade_id,
Timesheet_emp (FK) maps on to emp_ID,
Payroll_emp (FK) maps on to emp_ID.
I would strongly suggest not to write row level triggers for big business logic.
Row level triggers might have following problem
Trigger will execute for every row , it will be highly in-efficient and slow down the system.
Error handling will be cumbersome , if any one row- triggers has failed/error , then its fails sql update stm & difficult proceed.
very low chance of improving the performance , since trigger execute on row level.
Other developers could NOT find existence of a trigger .
Very difficult to enhance or debug later.
Triggers are difficult to test independently
implement in procedure
join table logically , don’t do row by row
have proper error handling procedure
test procedure independently
you have freedom of calling procedure any part of you code, may be during off peak hours.

Oracle trigger syntax

I have a question about my trigger that I'm trying to create between two tables. When one table is updated the other should be updated too, but I seem to be missing proper syntax.
CREATE OR REPLACE TRIGGER TRIG_DEPT_ONUPDATE
AFTER UPDATE OF DEPT_ID ON DEPARTMENT FOR EACH ROW
BEGIN
UPDATE TEAM
SET DEPT_ID = :NEW.DEPT_ID
WHERE TEAM.DEPT_ID = :NEW.DEPT_ID;
END;
/
I get errors on update ("integrity constraint (%s.%s) violated - child record found"), but using the code:
CREATE OR REPLACE TRIGGER TRIG_DEPT_ONUPDATE
AFTER UPDATE OF DEPT_ID ON DEPARTMENT FOR EACH ROW
BEGIN
UPDATE TEAM
SET DEPT_ID = :NEW.DEPT_ID;
END;
/
it changes every single row after an update, though only select few need the change. Should an If statement be worked in somehow?
To access the newly updated row values, you need a row level trigger not a statement level trigger:
CREATE OR REPLACE TRIGGER TRIG_DEPT_ONUPDATE
AFTER UPDATE OF DEPT_ID ON TEAM
for each row
BEGIN
UPDATE DEPARTMENT
SET DEPT_ID = :NEW.DEPT_ID
Where DEPT_ID = :OLD.DEPT_ID;
END;
I guess this row
DEPT_ID = DEPT_ID - :NEW.DEPT_ID
generates some DEPT_ID which is not existing. That's the error reason.