PLSQL Updating employee salary where department_id = 30 using cursor - oracle

I need to update employee salary from department 30. all employees from department 30 will have 20% salary increase. I run into error while executing my code.
Here is my code:
DECLARE
CURSOR cur_emp
IS
SELECT * FROM employees WHERE department_id = 30;
rec_emp cur_emp%rowtype;
BEGIN
OPEN cur_emp;
LOOP
FETCH cur_emp INTO rec_emp;
IF rec_emp.department_id = 30 THEN
UPDATE employees
SET salary = salary + (salary * 0.20)
WHERE employee_id = rec_emp.employee_id;
END IF;
EXIT
WHEN cur_emp%notfound;
END LOOP;
CLOSE cur_emp;
END;
/

You can handle the task only by one DML statement rather than a code block, after commenting out the parts of the trigger's code which leads to the current issue or disabling it of course, such as
SQL> UPDATE employees
SET salary = ROUND(salary * 1.2,2)
WHERE department_id = 30;
/
SQL> COMMIT;

Related

Why in my Procedure is not printing the correct value updated?

Here my code for test:
PROCEDURE INCREASE(p_employee in number)
is
V_salary employees.salary%type;
BEGIN
Select salary into v_salary
From employees where employee_id = p_employee;
if v_salary >= 15000 then
Update employees set salary = v_salary + ((v_salary * 20)/100) Where employee_id = p_employee;
else
Update employees set salary = v_salary + ((v_salary * 10)/100) Where employee_id = p_employee;
end if;
commit;
DBMS_OUTPUT.PUT_LINE('Name: ' || v_name || ' ' || 'salary: ' || v_salary );
End;
When I run the procedure and printed to see the output, we see the result before the update:
enter image description here
When I see the salary for this user is already updated when I select the table Employees:
enter image description here
Because you are not printing the updated value, you are printing the selected value. Your update changes the value of salary in the database, it does NOT change variable selected into. You need to return clause of the update statement. Moreover you don't need to select; this can be done in a single statement, except for setting up to variable and actually printing.
create or replace procedure increase(p_employee in number)
is
v_salary employees.salary%type;
begin
update employees
set salary = case when salary >= 15000
then 1.2 * salary
else 1.1 * salary
end
where employee_id = p_employee
return salary into v_salary;
commit;
dbms_output.put_line('mesalao: ' || v_salary );
end;

trigger to check avg salary of the department before inserting new record

lets say employee table is as below
EID ENAME DEPTNO SALARY
1 john 10 100
2 jau 10 300
3 cau 10 200
4 cha 20 200
5 cwea 20 500
6 dan 20 200
7 an 20 300
I have to check if any new employee is added, the new employee salary should be greater than the average salary in that department, and this should be done in triggers.
so I have created trigger as below
create or replace trigger tg_emp before insert on employee for each row
declare
avgsal number;
highsalary EXCEPTION;
BEGIN
select avg(salary) into avgsal from employee where deptno = :NEW.deptno;
if :NEW.salary < avgsal
then
raise highsalary;
end if;
EXCEPTION
when highsalary then
Raise_Application_Error (-20343, 'salary is less than the avg salary in this
department');
WHEN others THEN
Raise_Application_Error (-20353, 'other error probably table mutation
error');
END;
as you know with this code it works for only individual inserts like below
insert into employee values (8, 'jj', 10, 500);
but if it is a multiple inserts at once like
insert into employee
select seq_emp.next, 'ffgg', 10, 400 from all_tab_columns where rownum < 5;
it throws table mutation error(I know the above insert does not make sense but I am using it as just an example for multi insert in one statement).
so how can we resolve this using global temporary tables?
I think I was able to solve it using 1 GTT and 1 before statement trigger and 1 before row trigger as below
CREATE GLOBAL TEMPORARY TABLE employee_GTT (
id NUMBER,
name VARCHAR2(20),
deptno number,
salary number
)
ON COMMIT DELETE ROWS;
statement level before trigger
create or replace trigger emp_avg_load before insert on employee
begin
insert into dept_avg
select deptno, avg(salary), count(deptno) from employee group by deptno;
dbms_output.put_line('getting data from GTT');
end;
row level before trigger
create or replace trigger tg_emp before insert on employee for each row
declare
avgsal number;
ct number;
highsalary EXCEPTION;
BEGIN
avgsal := :new.salary;
select avgsal, count into avgsal, ct from dept_avg where deptno =
:NEW.deptno;
if :NEW.salary < avgsal
then
raise highsalary;
else
update dept_avg
set count = count +1,
avgsal = (avgsal+:NEW.salary)/(count+1)
where deptno = :NEW.deptno;
end if;
EXCEPTION
when highsalary then
Raise_Application_Error (-20343, 'salary is less than the avg salary in this
department');
WHEN others THEN
Raise_Application_Error (-21343, 'some other error');
END;
Please correct me if I get it wrong.
The way you use the temporary table is good idea at first.
But the way you update the average salary during the transaction looks wrong to me. Indeed, depending on how Oracle handles the update (order of inserts), you won't have the same results.
First, since you only insert employee when salary is above average, then average can only increase.
Now , if highest salary is inserted first, then average might increase too much for the next employee to be inserted.
You are having difficulties because your requirement isn't clear enough.
I do not think it is real use case to change the average salary while you are inserting the rows. You must think of it at a transaction level: thus, average salary is defined before transaction happens. So no need to have the average change while you are inserting. Just let the average stay the same during transaction.
I would remove this part:
else
update dept_avg
set count = count +1
, avgsal = (avgsal+:NEW.salary)/(count+1)
where deptno = :NEW.deptno;

What considerations Do You have about my PLSQL package?

I had a little doubts about my code but yesterday I finally understood some points about to how to start coding my final package project. I share this code with the purpose if You want suggest me some change to perform or anything about my package I will appreciate you.
CREATE OR REPLACE PACKAGE BODY emp_upd_pkg IS
-- Function to update commission of employee --
FUNCTION comm_upd(
p_empid employees.employee_id%TYPE)
RETURN employees.commission_pct%TYPE
IS
v_oldcomm employees.commission_pct%TYPE;
v_newcomm employees.commission_pct%TYPE;
BEGIN
-- Valid parameter --
SELECT commission_pct
INTO v_oldcomm
FROM employees
WHERE employee_id = p_empid;
IF
v_oldcomm IS NOT NULL THEN
UPDATE employees
SET commission_pct = commission_pct * 1.1
WHERE employee_id = p_empid
RETURNING commission_pct
INTO v_newcomm;
RETURN v_newcomm;
ELSE
/*UPDATE employees
SET commission_pct = 0.1
WHERE employee_id = p_empid
RETURNING commission_pct
INTO v_newcomm;
RETURN v_newcomm;*/
RETURN (0);
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN (0);
END comm_upd;
-- Function to update salary of employee --
FUNCTION sal_upd(
p_empid employees.employee_id%TYPE)
RETURN employees.salary%TYPE
IS
v_oldsal employees.salary%TYPE;
v_newsal employees.salary%TYPE;
BEGIN
-- Valid parameter --
SELECT salary
INTO v_oldsal
FROM employees
WHERE employee_id = p_empid;
IF
v_oldsal IS NOT NULL THEN
UPDATE employees
SET salary = salary + 100
WHERE employee_id = p_empid
RETURNING salary
INTO v_newsal;
RETURN v_newsal;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN (0);
END sal_upd;
-- Procedure to update comm and sal using package functions --
PROCEDURE commsal_upd(
p_empid employees.employee_id%TYPE)
IS
v_newcomm employees.commission_pct%TYPE;
v_newsal employees.salary%TYPE;
BEGIN
-- Call package functions to update sal and comm of all employees --
DBMS_OUTPUT.PUT_LINE(comm_upd(p_empid));
DBMS_OUTPUT.PUT_LINE(sal_upd(p_empid));
-- Query for final inform --
SELECT commission_pct, salary
INTO v_newcomm, v_newsal
FROM employees
WHERE employee_id = p_empid;
DBMS_OUTPUT.PUT_LINE('THE NEW COMMISSION FOR EMPLOYEE' || p_empid ||
' IS ' || v_newcomm || ' AND THE NEW SALARY IS ' || v_newsal);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('NO EXISTE EMPLEADO INGRESADO');
END commsal_upd;
END emp_upd_pkg;
Also, I have a little question: When I do use of a function within a procedure, Can I restringe the "RETURN" sentence of the function with the propuse of only send to call Procedure information?
SET SERVEROUTPUT ON
DECLARE
CURSOR cur_empid IS
SELECT employee_id
FROM employees;
TYPE empid_rec IS RECORD(
p_empid employees.employee_id%TYPE);
empid empid_rec;
BEGIN
FOR empid IN cur_empid LOOP
emp_upd_pkg.commsal_upd(empid.employee_id);
EXIT WHEN cur_empid%NOTFOUND;
END LOOP;
END;
/
When I use a simple record to update all employees I receive in console information about RETURN info of functions and info about DBMS... of procedure. Can I change my code to receive only Procedure information on console? Thanks!.
0
24100
THE NEW COMMISSION FOR EMPLOYEE100 IS AND THE NEW SALARY IS 24100
0
17100
THE NEW COMMISSION FOR EMPLOYEE101 IS AND THE NEW SALARY IS 17100
0
17100
THE NEW COMMISSION FOR EMPLOYEE102 IS AND THE NEW SALARY IS 17100
0
9100

PL/SQL invalid ROWID for update

I try update salary, but get error:
invalid ROWID
Cause: A ROWID was entered incorrectly. ROWIDs must be entered as
formatted hexadecimal strings using only numbers and the characters A
through F. A typical ROWID format is '000001F8.0001.0006'.
this is my code
DECLARE
CURSOR get_sls(mgr NUMBER, dep VARCHAR2) IS
SELECT *
FROM emp_n_m
WHERE emp_n_m.mgr = mgr
FOR UPDATE OF emp_n_m.sal ;
BEGIN
OPEN get_sls(7902, 'SALES');
if (get_sls%notfound) then
dbms_output.put_line('incorrect mgr');
else
UPDATE emp_n_m
SET emp_n_m.sal = emp_n_m.sal + 50
WHERE CURRENT OF get_sls;
COMMIT;
end if;
CLOSE get_sls;
END;
/
this should work:
DECLARE
CURSOR get_sls(mgr NUMBER, dep VARCHAR2) IS
SELECT *
FROM emp_n_m
WHERE emp_n_m.mgr = mgr
FOR UPDATE OF emp_n_m.sal ;
v_emp get_sls%rowtype;
BEGIN
OPEN get_sls(7902, 'SALES');
FETCH get_sls INTO v_emp;
if (get_sls%notfound) then
dbms_output.put_line('incorrect mgr');
else
UPDATE emp_n_m
SET emp_n_m.sal = emp_n_m.sal + 50
WHERE CURRENT OF get_sls;
COMMIT;
end if;
CLOSE get_sls;
END;
/
Is the cursor really required in your case? Would it not be efficient to get this done in a DML operation?
All you want to do is to update salary for manager 7902 from dept 'SALES ...
UPDATE emp_n_m SET emp_n_m.sal = emp_n_m.sal + 50
WHERE emp_n_m.mgr = 7902
AND dept = 'SALES';
COMMIT;

How to use a variable from a cursor in the select statement of another cursor in pl/sql

I want to run a query, get the results and then iterate through the results of that query with another select statement using the values of the first statement in my 2nd statement (cursor).
I have 40 users in my db.
All the users have the same db schema structure.
I want to get the username via :
SELECT distinct username
from all_users
then use the user name to run a query like this:
Select lastname, firstname, email, email2 from username.member.
My results set will return multiple rows so I need a row type as well.
I have tried many different pl/sql combinations:
DECLARE
CURSOR client_cur IS
SELECT distinct username
from all_users
where length(username) = 3;
-- client cursor
CURSOR emails_cur (cli all_users.username%TYPE) IS
SELECT id, name
FROM cli.org;
BEGIN
FOR client IN client_cur LOOP
dbms_output.put_line('Client is '|| client.username);
FOR email_rec in client_cur(client.username) LOOP
dbms_output.put_line('Org id is ' ||email_rec.id || ' org nam ' || email_rec.name);
END LOOP;
END LOOP;
END;
/
and
DECLARE
CURSOR c1 IS
SELECT distinct username from all_users where length(username) = 3;
client c1%rowtype;
cursor c2 is Select id, name, allow_digest_flg from c1.username.org;
digest c2%rowtype;
-- declare record variable that represents a row fetched from the employees table
-- employee_rec c1%ROWTYPE;
BEGIN
-- open the explicit cursor and use it to fetch data into employee_rec
OPEN c1;
loop
FETCH c1 INTO client;
open c2;
loop
fetch c2 into digest;
DBMS_OUTPUT.PUT_LINE('digest is : ' || c2.id || ' and name is ' || c2.name || ' flg is ' || c2.allow_digest_flg );
end loop;
end loop;
END;
/
AND MANY VARIATIONS OF THESE.
Can someone help me.
THANKS
You need to use dynamic SQL to achieve this; something like:
DECLARE
TYPE cur_type IS REF CURSOR;
CURSOR client_cur IS
SELECT DISTING username
FROM all_users
WHERE length(username) = 3;
emails_cur cur_type;
l_cur_string VARCHAR2(128);
l_email_id <type>;
l_name <type>;
BEGIN
FOR client IN client_cur LOOP
dbms_output.put_line('Client is '|| client.username);
l_cur_string := 'SELECT id, name FROM '
|| client.username || '.org';
OPEN emails_cur FOR l_cur_string;
LOOP
FETCH emails_cur INTO l_email_id, l_name;
EXIT WHEN emails_cur%NOTFOUND;
dbms_output.put_line('Org id is ' || l_email_id
|| ' org name ' || l_name);
END LOOP;
CLOSE emails_cur;
END LOOP;
END;
/
Edited to correct two errors, and to add links to 10g documentation for OPEN-FOR and an example.
Edited to make the inner cursor query a string variable.
You can certainly do something like
SQL> ed
Wrote file afiedt.buf
1 begin
2 for d in (select * from dept)
3 loop
4 for e in (select * from emp where deptno=d.deptno)
5 loop
6 dbms_output.put_line( 'Employee ' || e.ename ||
7 ' in department ' || d.dname );
8 end loop;
9 end loop;
10* end;
SQL> /
Employee CLARK in department ACCOUNTING
Employee KING in department ACCOUNTING
Employee MILLER in department ACCOUNTING
Employee smith in department RESEARCH
Employee JONES in department RESEARCH
Employee SCOTT in department RESEARCH
Employee ADAMS in department RESEARCH
Employee FORD in department RESEARCH
Employee ALLEN in department SALES
Employee WARD in department SALES
Employee MARTIN in department SALES
Employee BLAKE in department SALES
Employee TURNER in department SALES
Employee JAMES in department SALES
PL/SQL procedure successfully completed.
Or something equivalent using explicit cursors.
SQL> ed
Wrote file afiedt.buf
1 declare
2 cursor dept_cur
3 is select *
4 from dept;
5 d dept_cur%rowtype;
6 cursor emp_cur( p_deptno IN dept.deptno%type )
7 is select *
8 from emp
9 where deptno = p_deptno;
10 e emp_cur%rowtype;
11 begin
12 open dept_cur;
13 loop
14 fetch dept_cur into d;
15 exit when dept_cur%notfound;
16 open emp_cur( d.deptno );
17 loop
18 fetch emp_cur into e;
19 exit when emp_cur%notfound;
20 dbms_output.put_line( 'Employee ' || e.ename ||
21 ' in department ' || d.dname );
22 end loop;
23 close emp_cur;
24 end loop;
25 close dept_cur;
26* end;
27 /
Employee CLARK in department ACCOUNTING
Employee KING in department ACCOUNTING
Employee MILLER in department ACCOUNTING
Employee smith in department RESEARCH
Employee JONES in department RESEARCH
Employee SCOTT in department RESEARCH
Employee ADAMS in department RESEARCH
Employee FORD in department RESEARCH
Employee ALLEN in department SALES
Employee WARD in department SALES
Employee MARTIN in department SALES
Employee BLAKE in department SALES
Employee TURNER in department SALES
Employee JAMES in department SALES
PL/SQL procedure successfully completed.
However, if you find yourself using nested cursor FOR loops, it is almost always more efficient to let the database join the two results for you. After all, relational databases are really, really good at joining. I'm guessing here at what your tables look like and how they relate based on the code you posted but something along the lines of
FOR x IN (SELECT *
FROM all_users,
org
WHERE length(all_users.username) = 3
AND all_users.username = org.username )
LOOP
<<do something>>
END LOOP;
Use alter session set current_schema = <username>, in your case as an execute immediate.
See Oracle's documentation for further information.
In your case, that would probably boil down to (untested)
DECLARE
CURSOR client_cur IS
SELECT distinct username
from all_users
where length(username) = 3;
-- client cursor
CURSOR emails_cur IS
SELECT id, name
FROM org;
BEGIN
FOR client IN client_cur LOOP
-- ****
execute immediate
'alter session set current_schema = ' || client.username;
-- ****
FOR email_rec in client_cur LOOP
dbms_output.put_line(
'Org id is ' || email_rec.id ||
' org nam ' || email_rec.name);
END LOOP;
END LOOP;
END;
/

Resources