Create a procedure to give bonuses to employees - sql

I'm a beginner of PL/SQL.
I need to create a procedure.
The procedure name is sp_emp_bonus that takes 3 parameters:
department_id
job_id
amount
The procedure will update of bonus of employees within the department_id and job_id and increase existing bonus by the amount.
After the update statement, print using DBMS_OUTPUT.PUT_LINE to print out the number of employees affected by update (hint use implicit cursor attribute).
If no employees found then an exception section should capture NO_DATA_FOUND and print employees not found. Also include error handling for WHEN OTHERS ... and print DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM,1,100));
How to write this?

You can use simple update statement and sql%rowcount in procedure as follows:
CREATE OR REPLACE PROCEDURE SP_EMP_BONUS (
P_DEPARTMENT_ID IN NUMBER,
P_JOB_ID IN NUMBER,
P_AMOUNT IN NUMBER
) AS
LV_UPDATED_COUNT NUMBER := 0;
BEGIN
UPDATE YOUR_TABLE
SET
SALARY = SALARY + AMOUNT
WHERE DEPARTMENT_ID = P_DEPARTMENT_ID
AND JOB_ID = P_JOB_ID;
LV_UPDATED_COUNT := SQL%ROWCOUNT;
IF LV_UPDATED_COUNT = 0 THEN
DBMS_OUTPUT.PUT_LINE('no records found');
ELSE
DBMS_OUTPUT.PUT_LINE('Number of records updated: ' || LV_UPDATED_COUNT);
END IF;
END SP_EMP_BONUS;
/

Hope this will answer your question:
create or replace procedure sp_emp_bonus(
ip_dep_id in departments.department_id%type,
ip_job_id in jobs.job_id%type,
amount in number
)
is
no_emp_found exception;
pragma exception_init(no_emp_found, -20101);
cnt integer := 0;
begin
update
employees e
set e.salary = e.salary + nvl(amount, 0)
where
e.department_id = ip_dep_id and
e.job_id = ip_job_id;
cnt := sql%rowcount;
if cnt = 0 then
raise no_emp_found;
else
dbms_output.put_line('Employees updated ' || cnt);
end if;
commit;
exception
when no_emp_found then
rollback;
dbms_output.put_line('No Employees found for given Department and Job');
when OTHERS then
rollback;
DBMS_OUTPUT.PUT_LINE(SUBSTR(SQLERRM,1,100));
end;
Thanks.

Related

PL/SQL CREATE PROCEDURE - Salary increase based on tenure

I have worked on this for a while but the code did not work and I could not figure out the correct solution. Did I miss something from the code? Thank you.
-- Question – The company wants to calculate the employees’ annual salary: --The first year of employment, the amount of salary is the base salary which is $10,000. --Every year after that, the salary increases by 5%. --Write a stored procedure named calculate_salary which gets an employee ID and --for that employee calculates the salary based on the number of years the employee has --been working in the company. (Use a loop construct to calculate the salary). --The procedure calculates and prints the salary. --Sample output: --First Name: first_name --Last Name: last_name --Salary: $9999,99 --If the employee does not exists, the procedure displays a proper message.
CREATE OR REPLACE PROCEDURE calculate_salary(EMPLOYEE_ID EMPLOYEES.EMPLOYEE_ID%TYPE) AS
increase FLOAT := 1.05;
base_salary NUMBER := 10000;
TENURE NUMBER;
SALARY NUMBER;
EMP_ID EMPLOYEES.EMPLOYEE_ID%TYPE;
FIRST_NAME EMPLOYEES.FIRST_NAME%TYPE;
LAST_NAME EMPLOYEES.FIRST_NAME%TYPE;
BEGIN
SELECT EMPLOYEE_ID, ROUND((SYSDATE - HIRE_DATE)/365,0), FIRST_NAME, LAST_NAME INTO EMP_ID,TENURE, FIRST_NAME, LAST_NAME
FROM EMPLOYEES
WHERE EMPLOYEE_ID = EMP_ID;
FOR i IN 0..TENURE LOOP
SALARY := base_salary * i;
END LOOP;
DBMS_OUTPUT.PUT_LINE ('First Name: '||FIRST_NAME);
DBMS_OUTPUT.PUT_LINE ('Last Name: '||LAST_NAME);
DBMS_OUTPUT.PUT_LINE ('Salary: '||TO_CHAR(SALARY,'$99,999.99'));
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('No Data Found!');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Error!');
END;
/
BEGIN
calculate_salary(1);
END;
/
The calculation in the FOR loop is wrong. In the first loop iteration you are setting SALARY to zero. In the second iteration, you are setting SALARY equal to base_salary. In the third iteration you are setting SALARY to double base_salary, etc. Also, in PL/SQL, FOR loop limits are inclusive. Hence your loop should start at 1 (one) and not 0 (zero).
The below code calculates the salary assuming that the increase is based on the current salary and not the base salary. Changes to your code are indicated by comments at the end of the changed line.
CREATE OR REPLACE PROCEDURE calculate_salary(EMPLOYEE_ID EMPLOYEES.EMPLOYEE_ID%TYPE) AS
increase FLOAT := 1.05;
base_salary NUMBER := 10000;
TENURE NUMBER;
SALARY NUMBER;
EMP_ID EMPLOYEES.EMPLOYEE_ID%TYPE;
FIRST_NAME EMPLOYEES.FIRST_NAME%TYPE;
LAST_NAME EMPLOYEES.FIRST_NAME%TYPE;
BEGIN
SELECT EMPLOYEE_ID, ROUND((SYSDATE - HIRE_DATE)/365,0), FIRST_NAME, LAST_NAME INTO EMP_ID,TENURE, FIRST_NAME, LAST_NAME
FROM EMPLOYEES
WHERE EMPLOYEE_ID = EMP_ID;
SALARY := base_salary; -- Added this line.
FOR i IN 1..TENURE LOOP -- Changed this line.
SALARY := SALARY * increase; -- Changed this line.
END LOOP;
DBMS_OUTPUT.PUT_LINE ('First Name: '||FIRST_NAME);
DBMS_OUTPUT.PUT_LINE ('Last Name: '||LAST_NAME);
DBMS_OUTPUT.PUT_LINE ('Salary: '||TO_CHAR(SALARY,'$99,999.99'));
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE ('No Data Found!');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE ('Error!');
END;

PL-SQL Block with substitution variables

I have to write PL/SQL Block that print salary of the people who have same job_title. Job_title must be entered in substitution variable. I struggle to print with 'dbms_output.put_line'. Also sql says that join is not right.
DECLARE
v_jobt VARCHAR2(50);
v_sal Number ;
BEGIN
SELECT j.job_title,e.salary INTO v_jobt, v_sal
FROM jobs j
JOIN EMPLOYEES e
ON JOBS.JOB_ID=EMPLOYEES.salary
WHERE j.job_title = '&job_title';
DBMS_OUTPUT.PUT_LINE ('Job Title is : ' ||v_jobt);
END;
1, Put 'where clause' after 'join'
2, Use the Alias names in the 'on' condition
3, Add one single quote before ...is:'
If you get the error message, please tell us what message you got.
Try this:
DECLARE
v_lname VARCHAR2(50);
v_sal Number;
BEGIN
SELECT j.job_title,e.salary INTO v_lname, v_sal
FROM jobs j
JOIN EMPLOYEES e
ON j.JOB_ID = e.JOB_ID
WHERE j.job_title = '&job_title';
DBMS_OUTPUT.PUT_LINE ('...is : ' ||v_lname);
END;

How to call a function within a procedure to update all records of a table on PLSQL?

I'm practicing PLSQL and I'm coding a package with 2 functions to update commission and the other one to update salary but now I want to create a procedure within the same package to update commission and salary for all employees using the functions on the package. Is it possible?
CREATE OR REPLACE PACKAGE BODY emp_upd_pkg IS
-- Function to update commission_pct --
FUNCTION comm_upd(
p_empid employees.commission_pct%TYPE)
RETURN NUMBER
IS
v_new_comm employees.commission_pct%TYPE;
BEGIN
UPDATE employees
SET commission_pct = commission_pct * 1.1
WHERE employee_id = p_empid;
SELECT commission_pct
INTO v_new_comm
FROM employees
WHERE employee_id = p_empid;
RETURN v_new_comm;
EXCEPTION
WHEN
NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20992, 'NO EXISTE EMPLEADO');
END comm_upd;
-- Function to update salary --
FUNCTION sal_upd(
p_empid employees.salary%TYPE)
RETURN employees.salary%TYPE
IS
v_newsal employees.salary%TYPE;
BEGIN
UPDATE employees
SET salary = salary + 350
WHERE employee_id = p_empid;
-- Consulta select para la salida del a funcion --
SELECT salary
INTO v_newsal
FROM employees
WHERE employee_id = p_empid;
RETURN v_newsal;
END sal_upd;
-- Procedure to update all records of employees table --
PROCEDURE comm_sal_upd(
p_new_comm employees.commission_pct%TYPE,
p_new_sal employees.salary%TYPE);
END emp_upd_pkg;
I've tried creating a cursor and fetching into functions but I didn't succeed.
PROCEDURE comm_sal_upd(
p_new_comm employees.commission_pct%TYPE,
p_new_sal employees.salary%TYPE)
IS
CURSOR emp_cur IS
SELECT commission_pct, salary
FROM employees;
BEGIN
OPEN emp_cur;
FETCH emp_cur
INTO emp_upd_pkg.comm_upd(p_comm), emp_upd_pkg.sal_upd(p_sal);
CLOSE emp_cur;
END comm_sal_upd;
You are using function so its returing a value. you must capture the value in your procedure as below:
PROCEDURE comm_sal_upd(
p_new_comm employees.commission_pct%TYPE,
p_new_sal employees.salary%TYPE)
IS
CURSOR emp_cur IS
SELECT commission_pct, salary
FROM employees;
var number;
var2 employees.salary%TYPE;
BEGIN
for rec in emp_cur
loop
var:= emp_upd_pkg.comm_upd(p_comm);
var2:=emp_upd_pkg.sal_upd(p_sal);
dbms_output.put_line('updated commission--'|| var || ' Updated Sal -- '|| var2);
end loop;
commit;
END comm_sal_upd;
First of all, few tricks to improve your functions.
You don't need to make two queries when you can make one. Instead of
v_new_comm employees.commission_pct%TYPE;
BEGIN
UPDATE employees
SET commission_pct = commission_pct * 1.1
WHERE employee_id = p_empid;
SELECT commission_pct
INTO v_new_comm
FROM employees
WHERE employee_id = p_empid;
RETURN v_new_comm;
use returning clause:
v_new_comm employees.commission_pct%TYPE;
BEGIN
UPDATE employees
SET commission_pct = commission_pct * 1.1
WHERE employee_id = p_empid
returning commission_pct
into v_new_comm;
RETURN v_new_comm;
Also, this exception block doesn't look like very helpful:
EXCEPTION
WHEN
NO_DATA_FOUND THEN
RAISE_APPLICATION_ERROR(-20992, 'NO EXISTE EMPLEADO');
You just change the language of the error message.
As for the procedure to update all records, there are several issues:
You should avoid update many records one by one, it significantly reduces the performance. If possible, update them all together:
UPDATE employees
SET commission_pct = commission_pct * 1.1,
salary = salary + 350;
Cursors don't work this way. You shold use them to select data.
To process all rows in cursors, use loops:
PROCEDURE comm_sal_upd(
p_new_comm employees.commission_pct%TYPE,
p_new_sal employees.salary%TYPE)
IS
CURSOR emp_cur IS
SELECT commission_pct, salary
FROM employees;
v_comission number;
v_salary number;
BEGIN
OPEN emp_cur;
loop
FETCH emp_cur INTO v_comission, v_salary;
EXIT WHEN emp_cur%NOTFOUND;
<do something>
end loop;
CLOSE emp_cur;
END comm_sal_upd;
With one FETCH you process only one record.
I think the following is the kind of thing you are trying to do. (Untested, probably has bugs.) Note this is the least efficient way to do anything in PL/SQL, so it's just to demonstrate how you might structure procedures that call each other.
(btw I don't know if there is some textbook out there that tells people to code in uppercase, but there is really no need to.)
create or replace package body emp_upd_pkg
as
-- Update commission_pct for one employee:
procedure comm_upd
( p_empid employees.employee_id%type
, p_new_comm employees.commission_pct%type )
is
begin
update employees set commission_pct = commission_pct * p_new_comm
where employee_id = p_empid;
if sql%rowcount = 0 then
raise_application_error(-20992, 'Commission update failed: employee id ' || p_empid || ' not found.', false);
end if;
end comm_upd;
-- Update salary for one employee:
procedure sal_upd
( p_empid employees.employee_id%type
, p_new_sal employees.salary%type )
is
begin
update employees set salary = salary + p_new_sal
where employee_id = p_empid;
if sql%rowcount = 0 then
raise_application_error(-20993, 'Salary update failed: employee id ' || p_empid || ' not found.', false);
end if;
end sal_upd;
-- Update all employees:
procedure comm_sal_upd
( p_new_comm employees.commission_pct%type
, p_new_sal employees.salary%type )
is
begin
for r in (
select employee_id from employees
)
loop
comm_upd(r.employee_id, p_new_comm);
sal_upd(r.employee_id, p_new_sal);
end loop;
end comm_sal_upd;
end emp_upd_pkg;

SumSalary 0 does not show

I'd like to know how can I get a message if my SUM salary is 0. Please check my code:
DECLARE
v_sum_sal NUMBER(10,2);
v_deptno NUMBER NOT NULL := 10;
BEGIN
SELECT SUM(salary) -- group function
INTO v_sum_sal FROM employees
WHERE department_id = v_deptno;
IF v_sum_sal = 0 THEN
DBMS_OUTPUT.PUT_LINE ('You have selected 0 employees ');
else
DBMS_OUTPUT.PUT_LINE ('The sum of salary is ' || v_sum_sal);
end if;
END;
If there are no matches, then the sum() returns NULL, not 0. Here are two approaches.
Change the query to:
SELECT COALESCE(SUM(salary), 0) -- group function
INTO v_sum_sal FROM employees
WHERE department_id = v_deptno;
Alternatively, change the if to:
IF v_sum_sal IS NULL THEN
Here what is happening in your code, if your table doesnt contain the dept id you provided. It will give a NULL output. So if you put a exception handling condition it will work like a charm.
DECLARE
v_sum_sal NUMBER(10,2);
v_deptno NUMBER NOT NULL := 10;
BEGIN
BEGIN
SELECT SUM(salary) -- group function
INTO v_sum_sal
FROM employees
WHERE department_id = v_deptno;
EXCEPTION
WHEN NO_DATA_FOUND THEN
v_sum_sal:=0;
END;
IF v_sum_sal = 0 THEN
DBMS_OUTPUT.PUT_LINE ('You have selected 0 employees ');
ELSE
DBMS_OUTPUT.PUT_LINE ('The SUM OF salary
IS
' || v_sum_sal);
END IF;
END;

I want to continue looping even when no-data-found and use index by..getting 'no data found error

I am using sqldeveloper. I have to use INDEX BY for this program.
Department table has ID's 10,20,50,60,80,100.
Currently the program prints dept_names for id's 10 and 20 and then quits.
DECLARE
TYPE dept_table_indexby is TABLE OF
departments.department_name%TYPE
INDEX BY PLS_INTEGER;
dept_table_arr dept_table_indexby;
v_department_id departments.department_id%TYPE := 10;
BEGIN
for i in 1..10 loop
BEGIN
select department_name into dept_table_arr(i)
from departments
where department_id = v_department_id;
v_department_id := v_department_id + 10;
EXCEPTION
WHEN no_data_found THEN
--null; /* tried this option, still control exits the loop */
dbms_output.put_line('in loop : ' || dept_table_arr(i));
end;
end loop;
for i in dept_table_arr.first..dept_table_arr.last loop
dbms_output.put_line('department name: outside loop ' || dept_table_arr(i));
end loop;
end;
Im not really sure where the error is.
My guess is because you are trying to asign NULL to dept_table_arr(i) when ID=30
Im not familiar with oracle sintaxis, but here are my suggestion:
1 - Easier but couldnt test it by myself.
SELECT COALESCE(department_name , '') into dept_table_arr(i)
FROM departments
WHERE department_id = v_department_id;
The COALESCE function takes two or more compatible arguments and returns the first argument that is not null.
2 - Longer but know will work
SELECT COUNT(department_name) into int_department
FROM departments
WHERE department_id = v_department_id;
IF int_department > 0 THEN
SELECT department_name into dept_table_arr(i)
FROM departments
WHERE department_id = v_department_id;
ELSE
dept_table_arr(i) := "";
END IF;
v_department_id := v_department_id + 10;
There are two issues in your code.
1)
select department_name into dept_table_arr(i)
from departments
where department_id = v_department_id;
v_department_id := v_department_id + 10;
When no_data_found occured v_department will not update value, after two iteration it will always remain 30.
2)
for i in dept_table_arr.first..dept_table_arr.last loop
dbms_output.put_line('department name: outside loop ' || dept_table_arr(i));
end loop;
In this loop i variable changes from 1 to 10 (10 times) but your array have only six values.
After fixing errors:
DECLARE
TYPE dept_table_indexby is TABLE OF
departments.department_name%TYPE
INDEX BY PLS_INTEGER;
dept_table_arr dept_table_indexby;
v_department_id departments.department_id%TYPE := 0; --start with 0
k number;
BEGIN
for i in 1..10 loop
BEGIN
v_department_id := v_department_id + 10; --update v_department_id before select
select department_name into dept_table_arr(i)
from departments
where department_id = v_department_id ;
EXCEPTION
WHEN no_data_found THEN
null; /* tried this option, still control exits the loop */
end;
end loop;
--Associative arrays must be processed with .first and .next
k:= dept_table_arr.first;
while (k is not null)
loop
-- dbms_output.put_line(k);
dbms_output.put_line('department name: outside loop ' || dept_table_arr(k) );
k:= dept_table_arr.next(k);
end loop;
end;
Use goto statement. http://www.tutorialspoint.com/plsql/plsql_goto_statement.htm
BEGIN
for i in 1..10 loop
BEGIN
<<loopstart>> /* Where to start looping again when reach exception*/
v_department_id := v_department_id + 10; --update in beginning like ksa said
select department_name into dept_table_arr(i)
from departments
where department_id = v_department_id;
EXCEPTION
WHEN no_data_found THEN
goto loopstart; /* Use this instead */
dbms_output.put_line('in loop : ' || dept_table_arr(i));
end;