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;
Related
I have a table EMP in my apex oracle database that contains an attribute of salary named sal. I have another table EMPSAL that has 3 attributes named averageSal, minSal, maxSal which are to be updated using triggers whenever any DML operation is performed on the EMP table's sal column. Here is the trigger used for upgrading:
create or replace trigger empsal_update_trigger
AFTER update on emp
for each row
declare
avgSal2 emp.sal%type;
minSal2 emp.sal%type;
maxSal2 emp.sal%type;
begin
select avg(sal), min(sal), max(sal) into avgSal2, minSal2, maxSal2 from emp;
delete from empsal;
insert into empsal values(avgSal2, minSal2, maxSal2);
end;
The insert and delete triggers work fine, but the update one given above gives error whenever A record is updated in EMPSAL. I have tried using before keyword instead of after but it's no use.
You don't need a row level trigger for this case, but use a statement level one. Even no need to use local variable definition through use of INSERT INTO ... SELECT... statement.
So, just remove FOR EACH ROW such as
CREATE OR REPLACE TRIGGER empsal_update_trigger AFTER UPDATE ON emp
BEGIN
DELETE empsal;
INSERT INTO empsal
SELECT AVG(sal), MIN(sal), MAX(sal)
FROM emp;
END;
/
I have emp table in schema1 and emp_fianl in schema2.
emp
empid ename estatus
1 abc incomplete
2 xyz complete
3 ifg incomplete
4 mno incomplete
Emp_final
empid ename estatus
2 xyz complete
I have to create a trigger to insert data in Schema2 emp_final table when the estatus in schema1 emp table changes to complete.
I have written below trigger for the same:
Create or replace trigger tri_emp_final
After update on emp
BEGIN
IF :new.estatus='complete' then
Insert into emp_final
(select :old.empid,:old.ename,:new.estatus from schem1.emp);
END IF;
END;
/
I am getting mutating error message for the above code. When I am trying to update the status in emp table. I am a java developer and do not have much experience in Oracle, SQL. Can anyone please help?
First thing: we you want to use :old and :new, your trigger MUST be FOR EACH ROW. So you need to change : before update on emp for each row.
Second: like Goran Stefanović wrote, you don't make that select to insert, just use the :old values.
Use
Create or replace trigger tri_emp_final
After update on emp for each row
BEGIN
IF :new.estatus='complete' then
Insert into emp_final ( empid , ename , estatus)
Values
(:old.empid,:old.ename,:new.estatus );
END IF;
END;
/
You can find a working demo here
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.
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.
I am trying to write a stored procedure that inserts a row into an employee table. If the department does not exist, that department needs to be inserted into the departments table. I have the following code:
drop table employees;
drop table departments;
create table departments(
dept varchar2(30),
dept_number number,
dept_city varchar2(30),
CONSTRAINT pk_dept PRIMARY KEY(dept)
);
create table employees(
dept varchar2(30),
employee_name varchar2(40),
employee_id number,
CONSTRAINT pk_id PRIMARY KEY(employee_id),
CONSTRAINT fk_dept FOREIGN KEY (dept) REFERENCES departments(dept)
);
CREATE OR REPLACE PROCEDURE employeeadd(
a_dept IN VARCHAR2,
a_employee_name IN VARCHAR2,
a_employee_id IN NUMBER)
as
li_count NUMBER;
BEGIN
sp_check_dept(a_dept, li_count);
if li_count = 0 then
INSERT INTO departments (dept) values (a_dept);
return;
end if;
INSERT INTO employee values (a_dept, a_employee_name, a_employee_id);
end;
/
create or replace procedure sp_check_dept(a_dept IN NUMBER,
a_count OUT NUMBER)
as
begin
select count(*)
into a_count
from departments
where dept_number = a_dept;
end;
/
When I run my execute statement as execute employeeadd('marketing', 'john', 10); I get the following errors. I can't seem to figure out how to get past the errors and/or write this correctly:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at "employeeadd", line 8
ORA-06512: at line 1
Why is li_count declared outside the BEGIN...END block? Do you need to assign it before sending it as an argument to sp_check_dept()?
Edit: Just saw your followup comment: sp_check_dept is expecting a number as its first parameter; you have declared a_dept as VARCHAR.
sp_check_dept takes a department number an input parameter (a NUMBER) and returns a count as an ouput parameter. employeeadd is passing a department name (a VARCHAR2) as the first parameter to sp_check_dept. There are a couple of ways to fix this. In general, you'll want a more consistent method of naming parameters to make it easier to identify these problems.
Option 1: Use the department name for both functions
create or replace procedure sp_check_dept(p_dept_name IN departments.dept%type,
p_count OUT NUMBER)
as
begin
select count(*)
into p_count
from departments
where dept = p_dept_name;
end;
/
CREATE OR REPLACE PROCEDURE employeeadd(
p_dept_name IN departments.dept%type,
p_employee_name IN employees.employee_name%type,
p_employee_id IN employees.employee_id%type)
as
li_count NUMBER;
BEGIN
sp_check_dept(p_dept_name, li_count);
if li_count = 0 then
INSERT INTO departments (dept)
VALUES (p_dept_name);
end if;
INSERT INTO employee(dept, employee_name, employee_id)
VALUES (p_dept, p_employee_name, p_employee_id);
end;
/
Option 2: Convert the department name in employeeAdd to the department number before passing it to sp_check_dept
create or replace procedure sp_check_dept(p_dept_number IN departments.dept_number%type,
p_count OUT NUMBER)
as
begin
select count(*)
into p_count
from departments
where dept_number = p_dept_number;
end;
/
CREATE OR REPLACE FUNCTION get_dept_number( p_dept_name IN departments.dept%tyep )
RETURN departments.dept_number%type
IS
l_dept_number departments.dept_number%type;
BEGIN
SELECT dept_number
INTO l_dept_number
FROM departments
WHERE dept = p_dept_name;
RETURN l_dept_number
END;
/
CREATE OR REPLACE PROCEDURE employeeadd(
p_dept_name IN departments.dept%type,
p_employee_name IN employees.employee_name%type,
p_employee_id IN employees.employee_id%type)
as
li_count NUMBER;
BEGIN
sp_check_dept( get_dept_number(p_dept_name), li_count);
if li_count = 0 then
INSERT INTO departments (dept)
VALUES (p_dept_name);
end if;
INSERT INTO employee(dept, employee_name, employee_id)
VALUES (p_dept, p_employee_name, p_employee_id);
end;
/
A couple of other observations
I removed the RETURN statement from your IF statement in employeeAdd. You almost certainly do not want to exit the procedure after inserting a row into the DEPARTMENTS table before inserting the row into the EMPLOYEE table.
Your table definition used the plural EMPLOYEES. Your procedure used the singular EMPLOYEE. I did not correct that because I wasn't sure whether the DDL you posted was incorrect or whether the procedure you posted was incorrect.
It would, in general, make far more sense for sp_check_dept to be implemented as a function that returned the count rather than as a procedure with an OUT parameter. If a piece of code simply exists to return data to the caller, it should be declared as a function.
From a data model standpoint, the column name DEPT isn't particularly good. It would be far more appropriate to use something like DEPARTMENT_NAME that conveys what the column actually represents.
From a data model standpoint, having the VARCHAR2 column DEPT (even if it is renamed to DEPARTMENT_NAME) as the primary key of DEPARTMENTS and the foreign key in EMPLOYEES does not make much sense. The primary key should be immutable. However the name of the department will change over time. It would make far more sense for the DEPARTMENT_NUMBER to be the primary key and for the DEPARTMENT_NAME to simply be marked as unique. That will make it far easier when the Marketing department gets renamed Advertising in the future because you won't have to chase down all the child tables to update them.
You should pick a naming convention for procedures and stick with that. I would prefer check_dept and add_employee (verb followed by subject, underscores separating words, no prefix). But if you wanted sp_check_dept and sp_add_employee or checkDept and addEmployee or even sp_dept_check and sp_employee_add that would be fine. But you'll drive yourself, and the other developers, crazy if there is no pattern to your procedure naming conventions.
2 possibilities I can see:
1. the employee table has columns in a different order than your insert statement and it's trying to convert dept or name to the id
2. the value set into li_count isn't a number so it's trying to convert the return value to a number and giving you the error