PL/SQL procedure, %TYPE - oracle

I have this task.
I did it but e need to use specific ID (tmp_emp_id EMPLOYEES.EMPLOYEE_ID%TYPE := 118;) or its giving me an error. How I can tell "check every ID".
Write a PL/SQL procedure to update the salary of an employee, provided as a parameter, by 5% if the salary exceeds the mid range of the salary against this job and update up to mid range if the salary is less than the mid range of the salary.
DECLARE
emp_min_salary NUMBER(6,0);
emp_max_salary NUMBER(6,0);
emp_mid_salary NUMBER(6,2);
tmp_salary EMPLOYEES.SALARY%TYPE;
tmp_emp_id EMPLOYEES.EMPLOYEE_ID%TYPE := 118;
tmp_emp_name EMPLOYEES.FIRST_NAME%TYPE;
BEGIN
SELECT min_salary,
max_salary
INTO emp_min_salary,
emp_max_salary
FROM JOBS
WHERE JOB_ID = (SELECT JOB_ID
FROM EMPLOYEES
WHERE EMPLOYEE_ID = tmp_emp_id);
-- calculate mid-range
emp_mid_salary := (emp_min_salary + emp_max_salary) / 2;
-- get salary of the given employee
SELECT salary,first_name
INTO tmp_salary,tmp_emp_name
FROM employees
WHERE employee_id = tmp_emp_id;
-- update salary
IF tmp_salary < emp_mid_salary THEN
UPDATE employees
SET salary = emp_mid_salary
WHERE employee_id = tmp_emp_id;
ELSE
UPDATE employees
SET salary = salary + salary * 5 /100
WHERE employee_id = tmp_emp_id;
END IF;
--display message
IF tmp_salary > emp_mid_salary THEN
DBMS_OUTPUT.PUT_LINE('The employee '||tmp_emp_name||' ID ' || TO_CHAR(tmp_emp_id) ||
' works in salary ' || TO_CHAR(tmp_salary) ||
' which is higher than mid-range of salary ' || TO_CHAR(emp_mid_salary));
ELSIF tmp_salary < emp_mid_salary THEN
DBMS_OUTPUT.PUT_LINE('The employee '||tmp_emp_name||' ID ' || TO_CHAR(tmp_emp_id) ||
' works in salary ' || TO_CHAR(tmp_salary) ||
' which is lower than mid-range of salary ' || TO_CHAR(emp_mid_salary));
ELSE
DBMS_OUTPUT.PUT_LINE('The employee '||tmp_emp_name||' ID ' || TO_CHAR(tmp_emp_id) ||
' works in salary ' || TO_CHAR(tmp_salary) ||
' which is equal to the mid-range of salary ' || TO_CHAR(emp_mid_salary));
END IF;
END;
/

NO_DATA_FOUND is returned by one of SELECT statements you used; can't tell which one, I don't have your data.
However, that can be simplified. Here's an example which shows how you might do that.
Sample data is based on Scott's EMP table.
Mid-salaries (using the same algorithm you used):
SQL> SELECT job, (MIN (sal) + MAX (sal)) / 2 midsal
2 FROM emp
3 GROUP BY job;
JOB MIDSAL
--------- ----------
CLERK 1050 --> I'll be using CLERKS for demonstration
SALESMAN 1425
PRESIDENT 5000
MANAGER 2712,5
ANALYST 3000
Clerks:
SQL> SELECT *
2 FROM employees
3 WHERE job = 'CLERK'
4 ORDER BY ename;
EMPNO ENAME JOB SAL
---------- ---------- --------- ----------
7876 ADAMS CLERK 1100
7900 JAMES CLERK 950
7934 MILLER CLERK 1300 --> salary is higher than mid-salary
7369 SMITH CLERK 800 --> salary is lower than mid-salary
Procedure: it uses MERGE as it does everything in the same step, no need for additional commands.
SQL> CREATE OR REPLACE PROCEDURE p_sal (par_empno IN employees.empno%TYPE)
2 IS
3 BEGIN
4 MERGE INTO employees e
5 USING ( SELECT job, (MIN (sal) + MAX (sal)) / 2 midsal
6 FROM emp
7 GROUP BY job) x
8 ON (e.job = x.job)
9 WHEN MATCHED
10 THEN
11 UPDATE SET
12 e.sal =
13 CASE WHEN e.sal > x.midsal THEN e.sal * 1.05 ELSE x.midsal END
14 WHERE e.empno = par_empno;
15 END;
16 /
Procedure created.
Testing:
SQL> EXEC p_sal(7369);
PL/SQL procedure successfully completed.
SQL> EXEC p_sal(7934);
PL/SQL procedure successfully completed.
SQL> SELECT *
2 FROM employees
3 WHERE job = 'CLERK'
4 ORDER BY ename;
EMPNO ENAME JOB SAL
---------- ---------- --------- ----------
7876 ADAMS CLERK 1100
7900 JAMES CLERK 950
7934 MILLER CLERK 1365 --> 5% raise
7369 SMITH CLERK 1050 --> set to mid-salary
SQL>

Personally I voted the solution of #littlefoot, since he answered properly about the original question of the NO_DATA_FOUND and also shrinked the code in 1 single merge statement as I would too.
I just want to add an alternate version in case you "need" to log salary changes and/or add more business logic for each specific case.
DECLARE
emp_mid_salary NUMBER(6,2);
tmp_salary EMPLOYEES.SALARY%TYPE;
new_salary EMPLOYEES.SALARY%TYPE;
tmp_emp_id EMPLOYEES.EMPLOYEE_ID%TYPE := 118;
tmp_emp_name EMPLOYEES.FIRST_NAME%TYPE;
BEGIN
--single query for extract all data
SELECT (min_salary + max_salary) / 2, salary, first_name
INTO emp_mid_salary, tmp_salary, tmp_emp_name
FROM JOBS J
JOIN EMPLOYEES E
ON J.JOB_ID = E.JOB_ID
WHERE E.EMPLOYEE_ID = tmp_emp_id
;
--business logic
IF tmp_salary > emp_mid_salary THEN
--calcs
new_salary := tmp_salary + tmp_salary * 5 /100;
--output
DBMS_OUTPUT.PUT_LINE('The employee '||tmp_emp_name||' ID ' || TO_CHAR(tmp_emp_id) ||
' works in salary ' || TO_CHAR(tmp_salary) ||
' which is higher than mid-range of salary ' || TO_CHAR(emp_mid_salary));
ELSIF tmp_salary < emp_mid_salary THEN
--calcs
new_salary := emp_mid_salary;
--output
DBMS_OUTPUT.PUT_LINE('The employee '||tmp_emp_name||' ID ' || TO_CHAR(tmp_emp_id) ||
' works in salary ' || TO_CHAR(tmp_salary) ||
' which is lower than mid-range of salary ' || TO_CHAR(emp_mid_salary));
ELSE
--calcs
new_salary := tmp_salary + tmp_salary * 5 /100;
--output
DBMS_OUTPUT.PUT_LINE('The employee '||tmp_emp_name||' ID ' || TO_CHAR(tmp_emp_id) ||
' works in salary ' || TO_CHAR(tmp_salary) ||
' which is equal to the mid-range of salary ' || TO_CHAR(emp_mid_salary));
END IF;
BEGIN
--one single final update
UPDATE employees
SET salary = new_salary
WHERE employee_id = tmp_emp_id;
EXCEPTION WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Failed to update salary to employee with ID ' || TO_CHAR(tmp_emp_id));
--handle/log exception
END;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Could not find a JOB related to the employee with ID ' || TO_CHAR(tmp_emp_id));
WHEN OTHERS THEN
--handle/log exception, in the rare case something went wrong with the calcs?
END;
/

Related

Unable to load the data into a nested table(collection)

I would like to load data into a collection from various cursors. and below you can see a small example of the same. To simply put, I need to take columns from various tables and load it into a single table. Initially we were using a direct insert statement which was working fine, however it is causing issues recently. So we are trying to introduce a collection which would collect the data and from there we will load the data to the table.
declare
vin date;
cursor c1 is select emp_id.. from emp;
cursor c2 is select dept_id , ... from dept;
type t3 is table of t%rowtype;
t1 t3 := t3();
begin
for i in (select emp_id,dept_id,vin from dual)
loop
t1.extend;
t1(t1.count).load_date := i.vin;
t1(t1.count).emp_id := i.emp_id;
t1(t1.count).dept_no := i.dept_id;
end loop;
forall j in 1..t1.last
insert into t values (t1(j).emp_id,t1(j).dept_id,t1(j).load_date);
end;
When i tried to run this one, I'm getting an error stating PLS00308 :that constructor is not allowed as the origin of the assignment. Kindly help me on this.
Try it like below:
SET SERVEROUTPUT ON
Declare
TYPE type_a_tbl is Table Of A_TBL%ROWTYPE INDEX BY BINARY_INTEGER;
i BINARY_INTEGER := 0;
tab_a_tbl type_a_tbl;
--
CURSOR c IS
Select e.EMPNO, e.ENAME, d.DEPTNO, d.DNAME, d.LOC, e.MGR
From EMP e
Inner Join DEPT d ON(d.DEPTNO = e.DEPTNO)
Where d.DEPTNO = 30;
cSet c%ROWTYPE;
--
Begin
OPEN c;
LOOP
FETCH c Into cSet;
EXIT WHEN c%NOTFOUND;
i := i + 1;
tab_a_tbl(i).EMPNO := cSet.EMPNO;
tab_a_tbl(i).ENAME := cSet.ENAME;
tab_a_tbl(i).DEPTNO := cSet.DEPTNO;
tab_a_tbl(i).DNAME := cSet.DNAME;
tab_a_tbl(i).LOC := cSet.LOC;
tab_a_tbl(i).MGR := cSet.MGR;
--
Insert Into A_TBL VALUES(tab_a_tbl(i).EMPNO,
tab_a_tbl(i).ENAME,
tab_a_tbl(i).DEPTNO,
tab_a_tbl(i).DNAME,
tab_a_tbl(i).LOC,
tab_a_tbl(i).MGR);
dbms_output.put_line('Row inserted: --> ' || tab_a_tbl(i).EMPNO || Chr(9) || Chr(9) ||
LPAD(tab_a_tbl(i).ENAME, 8, ' ') || Chr(9) || Chr(9) ||
tab_a_tbl(i).DEPTNO || Chr(9) || Chr(9) ||
tab_a_tbl(i).DNAME || Chr(9) || Chr(9) ||
tab_a_tbl(i).LOC || Chr(9) || Chr(9) ||
tab_a_tbl(i).MGR);
--
END LOOP;
CLOSE c;
End;
/
/*
anonymous block completed
Row inserted: --> 7499 ALLEN 30 SALES CHICAGO 7698
Row inserted: --> 7521 WARD 30 SALES CHICAGO 7698
Row inserted: --> 7654 MARTIN 30 SALES CHICAGO 7698
Row inserted: --> 7698 BLAKE 30 SALES CHICAGO 7839
Row inserted: --> 7844 TURNER 30 SALES CHICAGO 7698
Row inserted: --> 7900 JAMES 30 SALES CHICAGO 7698
*/
... or with two cursors (result is the same)
SET SERVEROUTPUT ON
Declare
TYPE type_a_tbl is Table Of A_TBL%ROWTYPE INDEX BY BINARY_INTEGER;
i BINARY_INTEGER := 0;
tab_a_tbl type_a_tbl;
--
CURSOR depts IS
Select d.DEPTNO, d.DNAME, d.LOC From DEPT d Where DEPTNO = 30;
deptSet depts%ROWTYPE;
--
CURSOR c IS
Select e.EMPNO, e.ENAME, e.MGR
From EMP e
Where e.DEPTNO = deptSet.DEPTNO;
cSet c%ROWTYPE;
--
Begin
OPEN depts;
LOOP
FETCH depts Into deptSet;
EXIT WHEN depts%NOTFOUND;
--
OPEN c;
LOOP
FETCH c Into cSet;
EXIT WHEN c%NOTFOUND;
i := i + 1;
tab_a_tbl(i).EMPNO := cSet.EMPNO;
tab_a_tbl(i).ENAME := cSet.ENAME;
tab_a_tbl(i).DEPTNO := deptSet.DEPTNO;
tab_a_tbl(i).DNAME := deptSet.DNAME;
tab_a_tbl(i).LOC := deptSet.LOC;
tab_a_tbl(i).MGR := cSet.MGR;
--
Insert Into A_TBL VALUES(tab_a_tbl(i).EMPNO,
tab_a_tbl(i).ENAME,
tab_a_tbl(i).DEPTNO,
tab_a_tbl(i).DNAME,
tab_a_tbl(i).LOC,
tab_a_tbl(i).MGR);
dbms_output.put_line('Row inserted: --> ' || tab_a_tbl(i).EMPNO || Chr(9) || Chr(9) ||
LPAD(tab_a_tbl(i).ENAME, 8, ' ') || Chr(9) || Chr(9) ||
tab_a_tbl(i).DEPTNO || Chr(9) || Chr(9) ||
tab_a_tbl(i).DNAME || Chr(9) || Chr(9) ||
tab_a_tbl(i).LOC || Chr(9) || Chr(9) ||
tab_a_tbl(i).MGR);
--
END LOOP;
CLOSE c;
--
END LOOP;
CLOSE depts;
End;
/
Don't use PL/SQL, cursors or collection. Just use INSERT INTO ... SELECT ... and JOIN the tables:
INSERT INTO t (emp_id, dept_id, load_date)
SELECT e.emp_id,
d.dept_id,
SYSDATE -- or the column where you stored the date
FROM emp e
INNER JOIN dept d
ON (e.dept_id = d.dept_id) -- or however you want to join the tables.

I'm stuck with this PLSQL function question, can you help me out?

Write a PLSQL function that checks whether the salary of an employee
is less than the average salary of all the employees. If the salary is lees,
the program should update the salary of that employee by 5% of their current salary and print
the old employee salary, the average salary of all the employees, and the new employee
salary after update. Your program should handle all the possible exceptions.
Your output should be formatted as below:
O/P: Old salary is: $510
Avg salary is: $957.05
New salary is: $765
1-Huguette Sandrine: $765
CREATE OR REPLACE FUNCTION checkEMPsal()
return number
IS
avg_sal number;
BEGIN
select AVG(emp_sal) into avg_sal from employee;
Function? I'd rather use a procedure here. Function returns some value, but - in this task - you're doing a lot more.
SQL> set serveroutput on
SQL> CREATE OR REPLACE PROCEDURE p_raise (par_empno IN emp.empno%TYPE)
2 IS
3 l_avg NUMBER;
4 l_sal_old emp.sal%TYPE;
5 l_sal_new emp.sal%TYPE;
6 BEGIN
7 -- average salary
8 SELECT ROUND (AVG (sal)) INTO l_avg FROM emp;
9
10 -- employee's salary
11 SELECT sal
12 INTO l_sal_old
13 FROM emp
14 WHERE empno = par_empno;
15
16 IF l_sal_old < l_avg
17 THEN
18 l_sal_new := l_sal_old * 1.05;
19
20 UPDATE emp
21 SET sal = l_sal_new
22 WHERE empno = par_empno;
23
24 DBMS_OUTPUT.put_line (
25 'old salary: '
26 || l_sal_old
27 || ', new salary: '
28 || l_sal_new
29 || ', average salary: '
30 || l_avg);
31 ELSE
32 DBMS_OUTPUT.put_line (
33 'Employee''s salary is not lower than average salary - no raise');
34 END IF;
35 EXCEPTION
36 WHEN NO_DATA_FOUND
37 THEN
38 DBMS_OUTPUT.put_line ('Employee not found');
39 END;
40 /
Procedure created.
For sample data in Scott's schema:
SQL> SELECT empno, ename, sal
2 FROM emp
3 ORDER BY sal;
EMPNO ENAME SAL
---------- ---------- ----------
7369 SMITH 840
7900 JAMES 950
7876 ADAMS 1100
7521 WARD 1250
7654 MARTIN 1250
7934 MILLER 1300
7844 TURNER 1500
7499 ALLEN 1600
7782 CLARK 2450
7698 BLAKE 2850
7566 JONES 2975
7788 SCOTT 3000
7902 FORD 3000
7839 KING 5000
14 rows selected.
Testing:
SQL> EXEC p_raise(7369);
old salary: 840, new salary: 882, average salary: 2076
PL/SQL procedure successfully completed.
SQL> EXEC p_raise(7788);
Employee's salary is not lower than average salary - no raise
PL/SQL procedure successfully completed.
SQL> EXEC p_raise(-1);
Employee not found
PL/SQL procedure successfully completed.
SQL>
Use a PROCEDURE (rather than a FUNCTION) and use SELECT ... FOR UPDATE ... and UPDATE ... RETURNING ... INTO ... to get the values to output:
CREATE PROCEDURE checkEMPsal(
v_id IN EMPLOYEES.ID%TYPE
)
IS
v_rowid ROWID;
v_name employees.name%TYPE;
v_old_salary employees.emp_sal%TYPE;
v_new_salary employees.emp_sal%TYPE;
v_average employees.emp_sal%TYPE;
BEGIN
SELECT AVG(emp_sal)
INTO v_average
FROM employees;
SELECT ROWID,
emp_sal,
name
INTO v_rowid,
v_old_salary,
v_name
FROM employees
WHERE id = v_id
FOR UPDATE OF emp_sal;
IF v_old_salary < v_average THEN
UPDATE employees
SET emp_sal = emp_sal * 1.05
WHERE ROWID = v_rowid
RETURNING emp_sal INTO v_new_salary;
ELSE
v_new_salary := v_old_salary;
END IF;
DBMS_OUTPUT.PUT_LINE('Old salary is: ' || TO_CHAR(v_old_salary, 'FM$999G990D00'));
DBMS_OUTPUT.PUT_LINE('Avg salary is: ' || TO_CHAR(v_average, 'FM$999G990D00'));
DBMS_OUTPUT.PUT_LINE('New salary is: ' || TO_CHAR(v_new_salary, 'FM$999G990D00'));
DBMS_OUTPUT.PUT_LINE(v_name || ': ' || TO_CHAR(v_new_salary, 'FM$999G990D00'));
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Employee not found.');
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('Too many employees found.');
END;
/
Which, for the sample data:
CREATE TABLE employees (id, name, emp_sal) AS
SELECT LEVEL, CHR(64+LEVEL), LEVEL * 100 FROM DUAL CONNECT BY LEVEL <= 10;
Then:
BEGIN
DBMS_OUTPUT.ENABLE;
checkEmpSal(2);
checkEmpSal(7);
checkEmpSal(11);
END;
/
Outputs:
Old salary is: $200.00
Avg salary is: $550.00
New salary is: $210.00
B: $210.00
Old salary is: $700.00
Avg salary is: $551.00
New salary is: $700.00
G: $700.00
Employee not found.
fiddle

- ORA-06550: line 9, column 12: PLS-00103: Encountered the symbol "FETCH" when expecting one of the following:

#C:\Users\4\Desktop\dbdrop;
#C:\Users\4\Desktop\dbcreate;
SET SERVEROUTPUT ON;
DECLARE
ORDER_ID ORDERS.ODID%TYPE;
COMPANY_NAME ORDERS.CNAME%TYPE;
ORDER_DATE ORDERS.ODATE%TYPE;
CURSOR ord_cursor IS
SELECT ODID, CNAME, ODATE
FROM ORDERS
WHERE ODER_DATE< TRUNC(SYSDATE);
FETCH FIRST 5 ROWS ONLY;
BEGIN
OPEN ord_cursor;
LOOP
FETCH ord_cursor into ORDER_ID, COMPANY_NAME, ORDER_DATE;
DBMS_OUTPUT.PUT_LINE(' ');
DBMS_OUTPUT.PUT_LINE('ODER ID: '|| TO_CHAR(Order_Id));
DBMS_OUTPUT.PUT_LINE( 'ODER DATE: ' || ORDER_DATE );
DBMS_OUTPUT.PUT_LINE('COMPANY NAME: '|| COMPANY_NAME );
DBMS_OUTPUT.PUT_LINE( '------------');
DBMS_OUTPUT.PUT_LINE( '------------');
IF ord_cursor%NOTFOUND THEN
EXIT;
END IF;
END LOOP;
CLOSE ord_cursor;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
Error report -
ORA-06550: line 9, column 12:
PLS-00103: Encountered the symbol "FETCH" when expecting one of the following:
begin function pragma procedure subtype type
current cursor delete
exists prior
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
This:
WHERE ODER_DATE< TRUNC(SYSDATE);
FETCH FIRST 5 ROWS ONLY;
is wrong; either remove semi-colon in the first line (if your database supports FETCH clause), or entire second line.
Shorter version of your code is something like this (I don't have your tables so I fabricated one, based on Scott's EMP table):
SQL> create table orders as
2 select empno ordid, ename cname, hiredate odate
3 from emp
4 where deptno = 10;
Table created.
Code itself:
SQL> set serveroutput on
SQL> begin
2 for cur_r in
3 (select ordid, cname, odate
4 from orders
5 where odate < trunc(sysdate)
6 and rownum <= 5
7 )
8 loop
9 dbms_output.put_line('------------');
10 dbms_output.put_line('Order ID = ' || cur_r.ordid);
11 dbms_output.put_line('Order date = ' || to_char(cur_r.odate, 'dd.mm.yyyy'));
12 dbms_output.put_line('Company = ' || cur_r.cname);
13 end loop;
14 end;
15 /
------------
Order ID = 7782
Order date = 09.06.1981
Company = CLARK
------------
Order ID = 7839
Order date = 17.11.1981
Company = KING
------------
Order ID = 7934
Order date = 23.01.1982
Company = MILLER
PL/SQL procedure successfully completed.
SQL>
Your query ends before the FETCH clause and that is the issue.
You can use simple FOR loop and string concatenation as follows:
BEGIN
FOR CUR IN (SELECT 'ODER ID: '|| TO_CHAR(ODID) || CHR(10)
|| 'ODER DATE: ' || ODATE || CHR(10)
|| 'COMPANY NAME: ' || CNAME || CHR(10)
|| '------------' AS STR
FROM ORDERS
WHERE ODER_DATE< TRUNC(SYSDATE)
FETCH FIRST 5 ROWS ONLY)
LOOP
DBMS_OUTPUT.PUT_LINE(CUR.STR);
END LOOP;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLERRM);
END;
/

How to handle exception in cursor when given record is not found in a table

I am updating emp3 (same as emp table) using a Cursor For loop,
DECLARE
CURSOR incr_cur IS SELECT * FROM emp3 FOR UPDATE OF sal;
v_job emp3.job%TYPE := '&ENTER_Job';
v_cnt INTEGER := 0;
BEGIN
FOR r_l IN incr_cur LOOP
IF v_job IN (r_l.job) THEN
UPDATE emp3 SET sal = sal + 100 WHERE CURRENT OF incr_cur;
END IF;
END LOOP;
FOR r_l IN incr_cur LOOP
IF v_job IN (r_l.job) THEN
v_cnt := v_cnt + 1;
DBMS_OUTPUT.PUT_LINE('The Salary of ' || r_l.ename || ' is Incremented by 100 and the Updated Salary is: $' || r_l.sal);
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE('The Salary of '|| v_cnt ||' Employees are Updated');
END;
When executing the PL/SQL block it will ask for the job,
I give MANAGER, then the salary of the employees who are MANAGER is incremented by 100.
The emp3 table has 5 JOB categories CLERK, MANAGER, ANALYST, SALESMAN and PRESIDENT.
Then how to display the Message The Job is not listed so update is not possible., if a user inputs a JOB which is not in the table such as DEVELOPER.
I had tried with exception handling but could not get it to work.
There is no need for a separate step. Just attempt the update and if no rows were updated, say so. If you want it to be an exception, then raise one with raise_application_error.
Assuming this is a learning exercise and this is why you don't want to just do an ordinary update, you might do something like this:
declare
k_job constant emp3.job%type := '&JOB';
cursor employees_cur is
select * from emp3
where job = k_job
for update of sal;
v_update_count integer := 0;
v_payroll_increase integer := 0;
begin
for r in employees_cur loop
update emp3 set sal = sal + 100 where current of employees_cur;
dbms_output.put_line('Salary for ' || r.ename || ' is incremented by $100 from $' || r.sal || ' to $' || (r.sal +100));
v_update_count := v_update_count + 1;
v_payroll_increase := v_payroll_increase + 100;
end loop;
if v_update_count = 0 then
dbms_output.put_line('No staff are currently employed as ' || k_job ||'. Payroll is unchanged.');
else
dbms_output.put_line('Updated salary of '|| v_update_count ||' employee' || case when v_update_count <> 1 then 's' end||'.');
dbms_output.put_line('Payroll increased by $'||v_payroll_increase||'.');
end if;
end;
/
Enter value for job: SALESMAN
Salary for ALLEN is incremented by $100 from $1600 to $1700
Salary for WARD is incremented by $100 from $1250 to $1350
Salary for MARTIN is incremented by $100 from $1250 to $1350
Salary for TURNER is incremented by $100 from $1500 to $1600
Updated salary of 4 employees.
Payroll increased by $400.
PL/SQL procedure successfully completed.
For a nonexistent job, you get this:
Enter value for job: ASTRONAUT
No staff are currently employed as ASTRONAUT. Payroll is unchanged.
(In this example, v_payroll_increase is always 100 times v_update_count, but if you wanted to give a 10% raise or differing increases by department etc it might be more useful.)
Here's one option: check whether such a job exists; if not, query will return NO_DATA_FOUND which you can handle and raise an exception with appropriate message. Otherwise, proceed with the UPDATE.
SQL> declare
2 l_job emp.job%type;
3 begin
4 begin
5 select job
6 into l_job
7 from emp
8 where job = '&ENTER_Job'
9 and rownum = 1;
10 exception
11 when no_data_found then
12 raise_application_error(-20000, 'That job does not exist');
13 end;
14
15 -- Job exists, so - go on with the update
16 end;
17 /
Enter value for enter_job: MANAGER
PL/SQL procedure successfully completed.
SQL> /
Enter value for enter_job: DEVELOPER
declare
*
ERROR at line 1:
ORA-20000: That job does not exist
ORA-06512: at line 12
SQL>
P.S. Forgot to mention: I prefer doing such a job through a stored procedure (which accepts job name as a parameter) instead of an anonymous PL/SQL block.

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