Guys I have the following problem:
Increase salary 15% for employees whose salary is less than 50% of their manager's salary.
Write PL/SQL procedure using cursor, loop and update.
Procedure header
Create or replace procedure inc_salary is:
. Exception if their salary after increase is more than 50% of their manager's salary.
Actually, we can do it directly like this:
update emp e
set e.salary+=e.salary*0.15
where e.salary<(select e.mgr from emp e, group by e.mgr)
Here is a picture of this table:
But I don't understand how to use the procedure. If I declare it like this, create or replace procedure inc_salary, then what should be its parameters? We can use of course loop, like
declare
for r in (select * from emp e) loop
update emp e
set r.salary+=r.salary*0.15;
where r.salary<r.mgr
exception
if r.salary >r.mgr*1.15 then
dbms.output_putline(' it can't increase');
end loop;
end;
But how to combine it together?
Why would you need a PL/SQL procedure? A simple query would do this job!
UPDATE emp
SET salary = salary * 1.15
WHERE empno IN (
SELECT e.empno
FROM emp e
JOIN emp m ON e.mgr = m.empno
WHERE e.salary < m.salary * 0.5
)
That's it!
But, if at all you need to use a procedure, you have to decide for yourself what exactly you want to do with it.
Every procedure has a set of formal parameters, which can even be an empty set. It is you who decides what to pass to a procedure. Consult your manager or architect for these situations.
declare
prec number;
procedure inc_salary(prcin number)
is
cursor cl is * from employees;
msal number(8,2);
mid number(6);
begin
for r in cl loop
mid := nvl(r.manager_id, r.employee_id);
select salary into msal from employees where employee_id = mid;
if r.salary < (msal * 0.5) then
update employees set
salary = salary * prc
where employee_id = r.employee_id;
end if;
end loop;
end inc_salary;
begin
prec := 1.5;
inc_salary(prec);
end ;
Related
I want to create a procedure/function, when user input a year and a department name, i need to display the employees' name (first and last name). This is my code so far. Any idea? Thanks!
DECLARE
CURSOR emp_cur (my_date int,my_dept departments.department_name%type) IS
SELECT e.first_name,e.last_name
FROM employees e
join departments d
on e.department_id=d.department_id
where d.department_name=my_dept and my_date=extract(year from e.hire_date);
PROCEDURE emp_excesspaid IS
v_first employees.first_name%TYPE;
v_last employees.last_name%TYPE;
BEGIN
LOOP
FETCH emp_cur INTO v_first, v_last;
EXIT WHEN emp_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_first||' '||v_last);
END LOOP;
END;
end;
You can make it simple as follows:
CREATE OR REPLACE PROCEDURE PROC_NAME(
P_IN_DEPT_NAME IN VARCHAR2,
P_IN_YEAR IN NUMBER
) AS
BEGIN
For D in (SELECT e.first_name, e.last_name
FROM employees e
join departments d
on e.department_id = d.department_id
where d.department_name = P_IN_DEPT_NAME
and extract(year from e.hire_date) = P_IN_YEAR)
LOOP
DBMS_OUTPUT.PUT_LINE(D.FIRST_NAME || ' ' || D.LAST_NAME);
END LOOP;
END;
/
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;
I have a very simple script to try the cursor function but it reported invalid CREATE COMMAND. Anyone know what could be the cause? Thank you!
create lot_1 cursor for select * from temp_minj1.lot
In Oracle, the SQL syntax is thus :
DECLARE
my_emp_id NUMBER(6); -- variable for employee_id
my_job_id VARCHAR2(10); -- variable for job_id
my_sal NUMBER(8,2); -- variable for salary
CURSOR c1 IS SELECT employee_id, job_id, salary FROM employees
WHERE salary > 2000;
my_dept departments%ROWTYPE; -- variable for departments row
CURSOR c2 RETURN departments%ROWTYPE IS
SELECT * FROM departments WHERE department_id = 110;
see source
CREATE OR REPLACE
PROCEDURE P_raise
AS
v_salary NUMBER;
v_first_name VARCHAR2(20);
v_min_salary NUMBER;
BEGIN
SELECT first_name,
salary
INTO v_first_name,
v_salary
FROM employees
WHERE salary =
(SELECT MIN(salary) FROM employees
);
dbms_output.put_line(v_first_name||' has a minimum salary of '||v_salary);
SELECT MIN(salary) INTO v_min_salary FROM employees;
IF v_salary = v_min_salary THEN
UPDATE employees SET salary = salary*1.15 WHERE salary=v_min_salary;
dbms_output.Put_line(v_first_name||' has an increase of 15% in his salary ');
END IF;
END;
/
This messy code is used to find the min(salary) and then give it a raise.
There are two minimum salary records with the same value. How do I rework my code to by pass the error?
Thanks
You can't use SELECT INTO if the statement doesn't return exactly one row. Here you could use a loop, for instance with an implicit cursor:
CREATE OR REPLACE PROCEDURE P_raise AS
BEGIN
FOR cc IN (SELECT first_name, salary, ROWID
INTO v_first_name, v_salary
FROM employees
WHERE salary = (SELECT MIN(salary) FROM employees)
FOR UPDATE NOWAIT) LOOP
dbms_output.put_line(cc.first_name || ' has a minimum salary of '
|| cc.salary);
UPDATE employees SET salary = salary * 1.15 WHERE ROWID = cc.rowid;
dbms_output.Put_line(cc.first_name
|| ' has an increase of 15% in his salary');
END LOOP;
END;
Or use a straight update (which would be more efficient in general, although you won't have the details of which row got updated):
CREATE OR REPLACE PROCEDURE P_raise AS
BEGIN
UPDATE employees
SET salary = salary * 1.15
WHERE salary = (SELECT MIN(salary) FROM employees);
dbms_output.put_line(SQL%ROWCOUNT
|| ' employees had their salary increased by 15%');
END;
I need to do the following problem,
Write a procedure that decreases the salary by a 10% for all employees who earn
less than the current average salary.
Treat the content of table EMP as “all employees”. Have your procedure print the name and salary of the first
person whose salary is just below the average.
What should be the approach to solve the problem?
Write a procedure that decreases the salary by a 10% for all employees who earn less than the current average salary.
CREATE OR REPLACE PROCEDURE UPDATE_EMP IS
BEGIN
UPDATE EMP
SET SAL= SAL-(SAL*0.1)
WHERE SAL<(SELECT AVG(SAL) FROM EMP);
END;
AND THE OTHER ONE :
Have your procedure print the name and salary of the first person whose salary is just below the average.
SELECT e.ename
, e.sal
from
(select ename
, sal
from emp
where sal < (select avg(sal)
from emp
)
order by sal desc
)e
where ROWNUM =1;
Now I need to connect both.. How could I do that ...
It sounds like you are a bit fuzzy on what a stored procedure is and how it can help you do complicated tasks involving many SQL statements.
You follow these directions on how a construct a stored procedure.
http://www.devshed.com/c/a/Oracle/Oracle-Stored-Procedures/
Stored procedures are wonderful structures that allow you to put multiple SQL statements into one structure, saving out variables for use in the next SQL statement. So all you have to do is invoke the stored procedure, and all the sql statements are run, and your answer is returned or table modification is committed.
You need something like this:
CREATE OR REPLACE PROCEDURE UPDATE_EMP RETURN name, value IS
BEGIN
UPDATE EMP
SET SAL= SAL-(SAL*0.1)
WHERE SAL<(SELECT AVG(SAL) FROM EMP);
SELECT e.ename INTO name_to_return, e.sal INTO sal_to_return from
(select ename, sal from emp where sal < (select
avg(sal)from emp) order by sal desc)e where ROWNUM =1;
RETURN name_to_return, sal_to_return;
END;
The syntax may be a bit off, when you get it working, post your answer here as a new answer, and check mark it as the answer, and you are much more likely to get help like this in the future.
CREATE OR REPLACE PROCEDURE DISPLAY_EMP IS
IS
CURSOR emp_cur
IS
SELECT ename
,sal
FROM emp
WHERE SAL<( SELECT AVG(SAL) FROM EMP)
ORDER BY sal desc;
v_emp_row emp_cur%ROWTYPE;
BEGIN
--update all the employee having sal less than avg sal
UPDATE EMP
SET SAL= SAL-(SAL*0.1)
WHERE SAL<(SELECT AVG(SAL) FROM EMP);
--display all the employee having sal less than avg sal
OPEN emp_cur ;
LOOP
FETCH emp_cur INTO v_emp_row;
EXIT WHEN emp_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('EMPLOYEE NAME : '||v_emp_row.ename||' '
||'SALARY : '||v_emp_row.sal);
END LOOP;
CLOSE emp_cur;
END DISPLAY_EMP;
--call the display_emp proc to display all the emp
BEGIN
DISPLAY_EMP ;
END;
You need one procedure to do the update, and another function or select statement to print the name and salary of the first person whose salary is just below the average.
CREATE OR REPLACE PROCEDURE DISPLAY_EMP IS
IS
CURSOR emp_cur
IS
SELECT ename
,sal
FROM emp
WHERE SAL<( SELECT AVG(SAL) FROM EMP)
ORDER BY sal desc;
v_emp_row emp_cur%ROWTYPE;
BEGIN
--update all the employee having sal less than avg sal
UPDATE EMP
SET SAL= SAL-(SAL*0.1)
WHERE SAL<(SELECT AVG(SAL) FROM EMP);
--display all the employee having sal less than avg sal
OPEN emp_cur ;
LOOP
FETCH emp_cur INTO v_emp_row;
EXIT WHEN emp_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('EMPLOYEE NAME : '||v_emp_row.ename||' '
||'SALARY : '||v_emp_row.sal);
END LOOP;
CLOSE emp_cur;
END DISPLAY_EMP;
--call the display_emp proc to display all the emp
BEGIN
DISPLAY_EMP ;
END;