create or replace TRIGGER CHECK_GRADE
BEFORE INSERT OR UPDATE ON EMPLOYEE
FOR EACH ROW
DECLARE
LoSal NUMBER(8, 2);
HiSal NUMBER(8, 2);
Letter VARCHAR(1);
Sal NUMBER(8, 2);
BEGIN
IF(:NEW.GLetter IS NOT NULL)
THEN
Sal := NVL(:NEW.Salary, -1);
SELECT MinSalary, MaxSalary
INTO LoSal, HiSal
FROM GRADE
WHERE GLetter = :NEW.GLetter;
IF(NOT(Sal BETWEEN LoSal AND HiSal))
THEN
RAISE_APPLICATION_ERROR(-20001, 'Salary does not correspond to grade error! ' );
END IF;
ELSIF(:NEW.Salary IS NOT NULL)
THEN
SELECT GLetter
INTO Letter
FROM GRADE
WHERE :NEW.Salary BETWEEN MinSalary AND MaxSalary;
:NEW.GLetter := Letter;
END IF;
END;
This is the sql statement I run
basically what I want to do is that when a user tries to update the salary of an employee the trigger will automatically fill the grade of the employee which corresponds to his/her salary
update employee set salary = 25500 where employeeid = 1006;
Table for employee
has attributes like name department name and others and salary and grade
Table for Grade has
minsalary
max salary
gradeletter
Error starting at line : 2 in command -
update employee set salary = 25500 where employeeid = 1006
Error report -
ORA-20001: Salary does not correspond to grade error!
ORA-06512: at "gh12345.CHECK_GRADE", line 18
ORA-04088: error during execution of trigger 'gh12345.CHECK_GRADE'
The error is being raised because of this part here:
SELECT MinSalary, MaxSalary
INTO LoSal, HiSal
FROM GRADE
WHERE GLetter = :NEW.GLetter;
IF(NOT(Sal BETWEEN LoSal AND HiSal))
THEN
RAISE_APPLICATION_ERROR(-20001, 'Salary does not correspond to grade error! ' );
END IF;
you are setting the salary to the value 25500 which is out of the range defined in this table GRADE.
So you should do a query on this table and define a value for Salary that is within this range, or update the GRADE table for your new disired range.
I have 2 tables:
Employees(Employee_ID, First_name, Last_name, Email, Hire_date, Department_ID)
Departments(Department_ID, Department_name, Total_employees).
I have created trigger that fires after inserting an employee to employees table. It increases the total_employees column by 1 for :new inserted Department_ID.
Trigger:
create or replace TRIGGER emp_count_department
AFTER INSERT OR DELETE ON EMPLOYEES
for each row
DECLARE
counter NUMBER;
BEGIN
if inserting then
select total_employees into counter
from departments
where department_id = :new.department_id;
update departments
set total_employees = counter+1
where department_id = :new.department_id;
elsif deleting then
select total_employees into counter
from departments
where department_id = :old.department_id;
update departments
set total_employees = counter-1
where department_id = :old.department_id;
end if;
END;
When I try to insert an employee, it is inserted but it doesn't increase total_employees by 1 in departments table. When I try to insert an employee with a department_ID that doesn't exist in departments table, it gives me error. So the trigger is working but not increasing the total numbers by 1.
Try this:
create or replace TRIGGER emp_count_department
AFTER INSERT OR DELETE ON EMPLOYEES
for each row
BEGIN
if inserting then
update departments
set total_employees = total_employees+1
where department_id = :new.department_id;
elsif deleting then
update departments
set total_employees = total_employees-1
where department_id = :old.department_id;
end if;
END;
I did simple testing of your code in Oracle 11g and it seems to work.
Please check if you missed in adding information.
create table emp1(emp_id number ,dept_id number);
create table dept1 (dept_id number ,total_emp number);
CREATE OR replace TRIGGER emp_count_dept
AFTER INSERT OR DELETE ON emp1
FOR EACH ROW
DECLARE
counter NUMBER;
BEGIN
IF inserting THEN
UPDATE dept1
SET total_emp = total_emp + 1
WHERE dept_id = :new.dept_id;
ELSIF deleting THEN
UPDATE dept1
SET total_emp = total_emp - 1
WHERE dept_id = :old.dept_id;
END IF;
END;
insert into dept1 values(10,10);
insert into emp1 values(10,10);
select * from dept1;
DEPT_ID TOTAL_EMP
10 11
I think you are missing an important use case, what if the employees table is updated and the dept_id changes. To expand upon the answer #pmdba gave, I think the following code would work best.
CREATE OR REPLACE TRIGGER emp_count_dept
AFTER INSERT OR UPDATE OF DEPT_ID OR DELETE ON employees
FOR EACH ROW
BEGIN
IF :new.dept_id IS NOT NULL THEN
UPDATE departments d
SET d.total_employees = NVL(d.total_employees, 0)+1
WHERE d.department_id = :new.dept_id;
END IF;
IF :old.dept_id IS NOT NULL THEN
UPDATE departments d
SET d.total_employees = d.total_employees - 1
WHERE d.department_id = :old.dept_id;
END IF;
END;
/
This takes care of UPDATE statements now. And also takes care of the first INSERT for a given department (If you didn't initialize all departments.total_employee to 0 when you created them).
I also created this DBFiddle demonstrating the functionality (And why you need the NVL function or to default your TOTAL_EMPLOYEES column to 0) (Link)
These are the list of tables I have created and inserted values for the created tables:
CREATE TABLE DEPARTMENT
(DEPARTMENT_ID NUMBER PRIMARY KEY,
DEPARTMENT_NAME VARCHAR(30) NOT NULL
);
CREATE TABLE JOBS
(JOB_ID NUMBER PRIMARY KEY,
JOB_TITLE VARCHAR(35) NOT NULL,
MIN_SALARY DECIMAL NOT NULL,
MAX_SALARY DECIMAL NOT NULL
);
CREATE TABLE EMPLOYEES
(EMPLOYEE_ID NUMBER PRIMARY KEY,
FIRST_NAME VARCHAR(20) NOT NULL,
LAST_NAME VARCHAR(25) NOT NULL,
EMAIL VARCHAR(25) NOT NULL,
PHONE_NUMBER VARCHAR(20) NOT NULL,
HIRE_DATE DATE NOT NULL,
JOB_ID NUMBER NOT NULL,
SALARY DECIMAL NOT NULL,
DEPARTMENT_ID NUMBER NOT NULL,
CONSTRAINT emp_job_fk FOREIGN KEY(JOB_ID) REFERENCES JOBS(JOB_ID),
CONSTRAINT emp_department_fk FOREIGN KEY(DEPARTMENT_ID) REFERENCES DEPARTMENT(DEPARTMENT_ID)
);
INSERT INTO DEPARTMENT (DEPARTMENT_ID,DEPARTMENT_NAME)
VALUES(1,'IT');
INSERT INTO DEPARTMENT (DEPARTMENT_ID,DEPARTMENT_NAME)
VALUES(2,'Sales');
INSERT INTO JOBS (JOB_ID,JOB_TITLE,MIN_SALARY,MAX_SALARY)
VALUES (1,'IT Administrator',250000.00,50000.00);
INSERT INTO JOBS (JOB_ID,JOB_TITLE,MIN_SALARY,MAX_SALARY)
VALUES (2,'Salesman',200000.00,40000.00);
INSERT INTO EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE_NUMBER,HIRE_DATE,JOB_ID,SALARY,DEPARTMENT_ID)
VALUES (1,'Tony','Starc','starc#gmail.com','0123456789',TO_DATE('15/1/2008','DD/MM/YYYY'),1,45000.00,1);
INSERT INTO EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE_NUMBER,HIRE_DATE,JOB_ID,SALARY,DEPARTMENT_ID)
VALUES (2,'Bruce','Wayne','bruce#gmail.com','0123456788',TO_DATE('15/1/2009','DD/MM/YYYY'),1,40000.00,1);
INSERT INTO EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE_NUMBER,HIRE_DATE,JOB_ID,SALARY,DEPARTMENT_ID)
VALUES (3,'Larry','Ellison','larry#gmail.com','0123456787',TO_DATE('15/1/2010','DD/MM/YYYY'),1,30000.00,1);
INSERT INTO EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE_NUMBER,HIRE_DATE,JOB_ID,SALARY,DEPARTMENT_ID)
VALUES (4,'Steve','Jobs','steve#gmail.com','0123456786',TO_DATE('15/1/2011','DD/MM/YYYY'),2,35000.00,2);
INSERT INTO EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE_NUMBER,HIRE_DATE,JOB_ID,SALARY,DEPARTMENT_ID)
VALUES (5,'Remy','Lebeau','remy#gmail.com','0123456785',TO_DATE('15/1/2012','DD/MM/YYYY'),2,30000.00,2);
INSERT INTO EMPLOYEES (EMPLOYEE_ID,FIRST_NAME,LAST_NAME,EMAIL,PHONE_NUMBER,HIRE_DATE,JOB_ID,SALARY,DEPARTMENT_ID)
VALUES (6,'Clark','Kent','clark#gmail.com','0123456784',TO_DATE('15/1/2013','DD/MM/YYYY'),2,35000.00,2);
Now in my assignment question, I have been asked to solve the following question:
Write a function called fn_emps_per_dept_jc450912 to retrieve EMPLOYEE_ID,FIRST_NAME,LAST_NAME AND JOB_TITLE with a given DEPARTMENT_ID. This function should have DEPARTMENT_ID as input parameter and this function should have EMPLOYEE_ID,FIRST_NAME,LAST_NAME AND JOB_TITLE as output parameters. It should return TRUE if found and FALSE if not found.
In order to display the information of employees from a particular department, I have written the following function:
CREATE OR REPLACE FUNCTION fn_emps_per_dept_jc450912 (f_dept_id IN NUMBER,f_emp_id OUT NUMBER,f_first_name OUT VARCHAR,f_last_name OUT VARCHAR,f_job_title OUT VARCHAR)
RETURN BOOLEAN
AS
BEGIN
SELECT EMPLOYEE_ID,FIRST_NAME,LAST_NAME,JOB_TITLE
INTO f_emp_id,f_first_name,f_last_name,f_job_title
FROM EMPLOYEES,JOBS,DEPARTMENT
WHERE DEPARTMENT.DEPARTMENT_ID = EMPLOYEES.DEPARTMENT_ID
AND JOBS.JOB_ID = EMPLOYEES.JOB_ID
AND EMPLOYEES.DEPARTMENT_ID = f_dept_id;
RETURN TRUE;
EXCEPTION
WHEN NO_DATA_FOUND
THEN
DBMS_OUTPUT.PUT_LINE('Employee not found');
RETURN FALSE;
END fn_emps_per_dept_jc450912;
Function FN_EMPS_PER_DEPT_JC450912 compiled
As you can see from the above that the function has been successfully compiled.
Then I tried executing the function:
DECLARE
f_dept_id NUMBER;
f_emp_id NUMBER;
f_first_name VARCHAR(30) ;
f_last_name VARCHAR(30) ;
f_job_title VARCHAR(30);
f_return BOOLEAN;
BEGIN
f_dept_id := 1;
f_return := fn_emps_per_dept_jc450912(f_dept_id,f_emp_id,f_first_name,f_last_name,f_job_title);
DBMS_OUTPUT.PUT_LINE('Employee_ID: ' || f_emp_id);
DBMS_OUTPUT.PUT_LINE('First Name: ' || f_first_name);
DBMS_OUTPUT.PUT_LINE('Last Name: ' || f_last_name);
DBMS_OUTPUT.PUT_LINE('Job: ' || f_job_title);
END;
I'm getting the following error:
Error report -
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at "SYSTEM.FN_EMPS_PER_DEPT_JC450912", line 5
ORA-06512: at line 10
01422. 00000 - "exact fetch returns more than requested number of rows"
*Cause: The number specified in exact fetch is less than the rows returned.
*Action: Rewrite the query or change number of rows requested
I need the solution code for the execution body to fix this error.
Firstly please use modern join syntax (ANSI standard since 92!).
So your SELECT should be:
SELECT EMPLOYEE_ID,FIRST_NAME,LAST_NAME,JOB_TITLE
INTO f_emp_id,f_first_name,f_last_name,f_job_title
FROM EMPLOYEES INNER JOIN JOBS ON JOBS.JOB_ID = EMPLOYEES.JOB_ID
INNER JOIN DEPARTMENT ON DEPARTMENT.DEPARTMENT_ID = EMPLOYEES.DEPARTMENT_ID
WHERE EMPLOYEES.DEPARTMENT_ID = f_dept_id;
Next when you get a problem like this, you should just run the query in a query window, like this:
SELECT EMPLOYEE_ID,FIRST_NAME,LAST_NAME,JOB_TITLE
FROM EMPLOYEES INNER JOIN JOBS ON JOBS.JOB_ID = EMPLOYEES.JOB_ID
INNER JOIN DEPARTMENT ON DEPARTMENT.DEPARTMENT_ID = EMPLOYEES.DEPARTMENT_ID
WHERE EMPLOYEES.DEPARTMENT_ID = 1;
This will reveal the cause of your problem as the output looks like this
EMPLOYEE_ID FIRST_NAME LAST_NAME JOB_TITLE
----------- -------------------- ------------------------- -----------------------------------
1 Tony Starc IT Administrator
2 Bruce Wayne IT Administrator
3 Larry Ellison IT Administrator
I.e the field employees.department_id is not unique. Therefore on execution the stored procedure is trying to put multiple values in a single variable. This it cannot do, hence the error. The procedure compiles ok, because at compile time, the procedure can have no idea that it will be passed a parameter which results in more than one record in the result set.
I have a scenario like this :
I have three tables:
CREATE TABLE DEPT(DEPT_PK NUMBER NOT NULL,DEPT_NAME VARCHAR(10),DESCRIPTION VARCHAR(50));
CREATE TABLE EMPLOYEE(EMPLOYEE_PK NUMBER(4) NOT NULL PRIMARY KEY,DEPT_PK NUMBER(4),EMP_NAME VARCHAR2(50),ADDRESS VARCHAR2(200));
CREATE TABLE SALARY(SALARY_PK NUMBER NOT NULL,EMP_PK NUMBER(4),net_salary number(5,2));
Now i want to delete the record from dept table and whose dept_pk size is 4 and modify the table's dept_pk size to 3.
Note that emp table has dept_pk as foreign key and salary table has emp_pk as foreign key.
I need to write a pl/sql procedure or sql query for this.
Thanks in advance.
So you have 1st to delete from SALARY:
delete from SALARY where EMP_PK in
(
select EMPLOYEE_PK
from EMPLOYEE
where DEPT_PK in
(
select DEPT_PK from DEPT where length(DEPT_PK) = 4
);
);
Then from EMPLOYEE:
delete from EMPLOYEE
where DEPT_PK in
(
select DEPT_PK from DEPT where length(DEPT_PK) = 4
);
And now from DEPT:
delete from DEPT where length(DEPT_PK) = 4;
EDIT : if you really want to use PL/SQL, you just have to enclose the following code into begin and end for one shot execution:
begin
delete from SALARY where EMP_PK in
(
select EMPLOYEE_PK
from EMPLOYEE
where DEPT_PK in
(
select DEPT_PK from DEPT where length(DEPT_PK) = 4
);
);
delete from EMPLOYEE
where DEPT_PK in
(
select DEPT_PK from DEPT where length(DEPT_PK) = 4
);
delete from DEPT where length(DEPT_PK) = 4;
end;
/
or create a procedure for re-usability: add create or replace procedure proc_name is before begin.
I created this 4 tables:
create table terminatedEmployees (
empid number primary key,
dept number,
empname varchar2(50),
salary number
);
create table employees (
empid number primary key,
dept number,
empname varchar2(50),
salary number
);
create table payroll (
empid number primary key,
salary number,
CONSTRAINT fk_payemploy
FOREIGN KEY (empid)
REFERENCES employees(empid)
);
create table salaryAudit (
empid number primary key,
oldsal number,
newsal number,
datechanged date,
changedby varchar2(25),
CONSTRAINT fk_salaryaudit
FOREIGN KEY (empid)
REFERENCES employees(empid)
);
and now I'm trying to create a trigger in order to update two of them when employees table is updated:
CREATE TRIGGER trigger_updated_employees
AFTER UPDATE ON employees
FOR EACH ROW
when (old.salary != new.salary)
BEGIN
UPDATE INTO salaryAudit (newsal, oldsal)
VALUES(:new.salary, :old.salary);
UPDATE INTO payroll (salary)
VALUES(:new.salary);
END;
But I'm getting the error:
2/5 PL/SQL: SQL Statement ignored
2/12 PL/SQL: ORA-00903: invalid table name
4/5 PL/SQL: SQL Statement ignored
4/12 PL/SQL: ORA-00903: invalid table name
The three tables I'm calling in the trigger are ok and other triggers I created work...
Try something like this:
CREATE TRIGGER TRIGGER_UPDATED_EMPLOYEES
AFTER UPDATE ON EMPLOYEES
FOR EACH ROW
WHEN (OLD.SALARY <> NEW.SALARY)
BEGIN
MERGE INTO PAYROLL p
USING (SELECT :NEW.EMPID AS EMPID FROM DUAL) d
ON (p.EMPID = d.EMPID)
WHEN NOT MATCHED THEN
INSERT (EMPID, SALARY)
VALUES (:NEW.EMPID, :NEW.SALARY)
WHEN MATCHED THEN
UPDATE
SET SALARY = :NEW.SALARY;
MERGE INTO SALARYAUDIT a
USING (SELECT :NEW.EMPID AS EMPID FROM DUAL) d
ON (a.EMPID = d.EMPID)
WHEN NOT MATCHED THEN
INSERT (EMPID, OLDSAL, NEWSAL, DATECHANGED, CHANGEDBY)
VALUES (:NEW.EMPID, :OLD.SALARY, :NEW.SALARY, SYSDATE, 'SOME_USER')
WHEN MATCHED THEN
UPDATE
SET OLDSAL = :OLD.SALARY,
NEWSAL = :NEW.SALARY,
DATECHANGED = SYSDATE,
CHANGEDBY = 'SOME_USER';
END TRIGGER_UPDATED_EMPLOYEES;
Share and enjoy.
I got it working just correcting the UPDATE statements syntax and modifying the condition as #BobJarvis suggested, this is the final result:
CREATE TRIGGER trigger_updated_employees
AFTER UPDATE OF salary ON employees
FOR EACH ROW
when (old.salary <> new.salary)
BEGIN
UPDATE salaryAudit
SET (newsal, oldsal)
VALUES (:new.salary, :old.salary)
WHERE (salaryAudit.empid = old.empid);
UPDATE payroll
SET (salary)
VALUES (:new.salary)
WHERE (payroll.empid = old.empid);
END;
The concept is the same that #BobJarvis proposed but a lot simpler. Thanks