PL/SQL procedure to assign grades to the employees - oracle

create or replace procedure p2
as
CURSOR c1 IS
SELECT salary
FROM employee1
FOR UPDATE;
BEGIN
FOR employee_rec IN c1 LOOP
exit when c1%notfound;
if salary>20000 then
update employee1 set grade='A' WHERE CURRENT OF c1;
if salary>15000 then
update employee1 set grade='B' WHERE CURRENT OF c1;
if salary>10000 then
update employee1 set grade='C' WHERE CURRENT OF c1;
if salary<10000 then
update employee1 set grade='D' WHERE CURRENT OF c1;
end if;
end if;
end if;
end if;
END LOOP;
END p2;
An error shows up as salary is undeclared How can I make this code better to get the desired output???

Why not simply like this:
update employee1 set grade=
case
when salary>20000 then 'A'
when salary>15000 then 'B'
when salary>10000 then 'C'
when salary<10000 then 'D'
ELSE grade -- keep existing grade value
end;

Your error is because there is no variable salary. You have a cursor called employee_rec which contains a column called salary but you would need to reference that using employee_rec.salary.
If we fix that (and indent your code) then it compiles:
CREATE PROCEDURE p2 AS
CURSOR c1 IS
SELECT salary
FROM employee1
FOR UPDATE;
BEGIN
FOR employee_rec IN c1 LOOP
EXIT WHEN c1%NOTFOUND;
IF employee_rec.salary>20000 THEN
update employee1 set grade='A' WHERE CURRENT OF c1;
IF employee_rec.salary>15000 THEN
update employee1 set grade='B' WHERE CURRENT OF c1;
IF employee_rec.salary>10000 THEN
update employee1 set grade='C' WHERE CURRENT OF c1;
IF employee_rec.salary<10000 THEN
update employee1 set grade='D' WHERE CURRENT OF c1;
END IF;
END IF;
END IF;
END IF;
END LOOP;
END p2;
/
However, it isn't going to give you the output you want due to the nested IF statements. If you replace the nested IF statements with ELSIF:
CREATE OR REPLACE PROCEDURE p2 AS
CURSOR c1 IS
SELECT salary
FROM employee1
FOR UPDATE;
BEGIN
FOR employee_rec IN c1 LOOP
EXIT WHEN c1%NOTFOUND;
IF employee_rec.salary>20000 THEN
update employee1 set grade='A' WHERE CURRENT OF c1;
ELSIF employee_rec.salary>15000 THEN
update employee1 set grade='B' WHERE CURRENT OF c1;
ELSIF employee_rec.salary>10000 THEN
update employee1 set grade='C' WHERE CURRENT OF c1;
ELSIF employee_rec.salary<10000 THEN
update employee1 set grade='D' WHERE CURRENT OF c1;
END IF;
END LOOP;
END p2;
/
Then your procedure works (except when the salary is 10,000).
However, you could still make it more efficient by getting rid of the cursor:
CREATE OR REPLACE PROCEDURE p2 AS
BEGIN
UPDATE employee1
SET grade = CASE
WHEN salary > 20000 THEN 'A'
WHEN salary > 15000 THEN 'B'
WHEN salary > 10000 THEN 'C'
ELSE 'D'
END;
END p2;
/
db<>fiddle

It is not
if salary>20000 then
but
if employee_rec.salary>20000 then
Code you wrote can be simplified - up to a single UPDATE statement. However, if you're using PL/SQL for learning, CASE is probably a better option than nested IFs. Something like this example based on subset of Scott's EMP table.
Test data:
SQL> create table test as
2 select empno, ename, sal, 'x' grade
3 from emp
4 where deptno = 10;
Table created.
SQL> select * from test order by sal desc;
EMPNO ENAME SAL G
---------- ---------- ---------- -
7839 KING 5000 x
7782 CLARK 2450 x
7934 MILLER 1300 x
Procedure, testing & result:
SQL> create or replace procedure p2 as
2 cursor c1 is select sal from test for update;
3 begin
4 for employee_rec in c1 loop
5 update test set
6 grade = case when employee_rec.sal > 3000 then 'A'
7 when employee_rec.sal > 2000 then 'B'
8 when employee_rec.sal > 1000 then 'C'
9 when employee_rec.sal <= 1000 then 'D'
10 end
11 where current of c1;
12 end loop;
13 end;
14 /
Procedure created.
SQL> exec p2;
PL/SQL procedure successfully completed.
SQL> select * From test order by sal desc;
EMPNO ENAME SAL G
---------- ---------- ---------- -
7839 KING 5000 A
7782 CLARK 2450 B
7934 MILLER 1300 C
SQL>

Related

Oracle PL/SQL Return recordset and specific column(from recordset) in seperate variable

I need to return value from procedure, recordset and single value(extract from recordset)
CREATE TABLE EMP
(
EMP_ID number;
EMP_NAME varchar2(20);
EMP_FLAG char(1);
);
CREATE TABLE SALARY
(
EMP_ID number;
EMP_SAL number;
);
BEGIN
insert into EMP values(10,'John','N');
insert into EMP values(20,'May','Y');
insert into SALARY values(10,10000);
insert into SALARY values(10,25000);
END;
/
Here is my Procedure
CREATE OR REPLACE PROCEDURE TEST(Flag in char(1),curEMP out SYS_REFCURSOR,Sal out number)
v_sal number;
v_emp_id number;
v_name varchar2(20);
AS
open curEMP for
select e.emp_id
,e.emp_name
,s.sal
from emp e
left join sal s on e.emp_id = s.emp_id
where e.emp_flag = Flag;
loop
fetch curEMP into v_emp_id,v_name,v_Sal; --in real life there are more than 20+ fields
exit when curEMP%notfound;
end loop;
Sal := v_sal;
END TEST;
/
Script to call Procedure
set serveroutput on;
variable cr refcursor;
variable sal number;
exec TEST('Y',:cr,:sal);
print cr;
print sal;
Here is the output
Output
sal
25000
However, I need output like this
--Output from cursor list
emp_id emp_name sal
20 May 25000
--from sal variable
sal
25000
There are multiple issues with your table DDL and procedure, but I guess you have executed the procedure anyway.
To fetch all the records, You can use the only cursor and open it in the procedure, there is no need for looping through a cursor in your procedure.
see the procedure and its call in the following example:
SQL> CREATE OR REPLACE PROCEDURE TEST (
2 FLAG IN CHAR, -- size can not be given in the input parameter
3 CUREMP OUT SYS_REFCURSOR
4 --SAL OUT NUMBER -- no need of this parameter
5 ) AS
6 --V_SAL NUMBER; -- all variables are not needed
7 --V_EMP_ID NUMBER;
8 --V_NAME VARCHAR2(20);
9 BEGIN
10 OPEN CUREMP FOR SELECT
11 E.EMP_ID,
12 E.EMP_NAME,
13 S.EMP_SAL
14 FROM
15 EMP E
16 LEFT JOIN SALARY S ON E.EMP_ID = S.EMP_ID
17 WHERE E.EMP_FLAG = FLAG;
18 -- LOOP -- looping is not needed
19 -- FETCH CUREMP INTO
20 -- V_EMP_ID,
21 -- V_NAME,
22 -- V_SAL; --in real life there are more than 20+ fields
23 -- EXIT WHEN CUREMP%NOTFOUND;
24 -- END LOOP;
25
26 -- SAL := V_SAL;
27 END TEST;
28 /
Procedure created.
SQL>
Testing the procedure now.
SQL> VAR x REFCURSOR;
SQL> exec test('Y',:x);
PL/SQL procedure successfully completed.
SQL> print :x
EMP_ID EMP_NAME EMP_SAL
---------- -------------------- ----------
20 May 25000
SQL>
Cheers!!

Oracle Dynamically call cursor

I've two cursors both have almost similar code just a little difference in the group by condition, I want if the id is 1 or 2 e.t.c then it should open cur1 else cur2, basically one cursor at a time. Is there any possibility of achieving this?
if id in (1,2,3,4) then
cursor_value:= 'cur1';
else
cursor_value := 'cur2';
end if;
for i in cursor_value loop
end loop;
You can use an OPEN-FOR statement. Example:
DECLARE
TYPE EmpCurTyp IS REF CURSOR;
v_emp_cursor EmpCurTyp;
emp_record employees%ROWTYPE;
v_stmt_str VARCHAR2(200);
v_e_job employees.job%TYPE;
BEGIN
-- Dynamic SQL statement with placeholder:
v_stmt_str := 'SELECT * FROM employees WHERE job_id = :j';
-- Open cursor & specify bind argument in USING clause:
OPEN v_emp_cursor FOR v_stmt_str USING 'MANAGER';
-- Fetch rows from result set one at a time:
LOOP
FETCH v_emp_cursor INTO emp_record;
EXIT WHEN v_emp_cursor%NOTFOUND;
END LOOP;
-- Close cursor:
CLOSE v_emp_cursor;
END;
/
One option would be to include both cursor's SELECT statements into cursor FOR loop, along with a condition that chooses which one of them will be used. For example:
SQL> declare
2 id number := &par_id;
3 begin
4 for cur_r in (select * from emp
5 where deptno = 10
6 and id in (1,2,3,4)
7 union all
8 select * from emp
9 where deptno = 20
10 and id not in (1,2,3,4)
11 )
12 loop
13 dbms_output.put_Line(cur_r.deptno ||' '||cur_r.ename);
14 end loop;
15 end;
16 /
Enter value for par_id: 1 --> ID = 1, so use SELECT for DEPTNO = 10
old 2: id number := &par_id;
new 2: id number := 1;
10 CLARK
10 KING
10 MILLER
PL/SQL procedure successfully completed.
SQL> /
Enter value for par_id: 823 --> ID <> 1, so use SELECT for DEPTNO = 20
old 2: id number := &par_id;
new 2: id number := 823;
20 SMITH
20 JONES
20 SCOTT
20 ADAMS
20 FORD
PL/SQL procedure successfully completed.
SQL>
The simple way could be to use if else condition.
DECLARE
type cur REF CURSOR;
c cur;
BEGIN
IF id in (1,2,3,4) THEN
OPEN c FOR 'cursor query 1';
ELSE
OPEN c FOR 'cursor query 2';
END IF ;
END;
Cheers!!

How can get deptno number from user and print the all employees, belongs deptno 10 in Oracle 11g

How can get deptno number from user and print the all employees, belongs deptno 10 in Oracle 11g
**Answer is
declare
cursor a(T number) is
Select *from EMp where Deptno = T;
Em a%rowtype;
begin
open a(&b);
loop
Fetch a into Em;
exit when a%notfound;
dbms_output.put_line(Em.EName);
end loop;
END;
/**
declare
cursor a(T number) is
Select *from EMp where Deptno = T;
Em a%rowtype;
begin
open a(&b);
loop
Fetch a into Em;
exit when a%notfound;
dbms_output.put_line(Em.EName);
end loop;
END;
/
You can simplify your code by using an implicit cursor ( T must be provided to the code block below, of course):
BEGIN
FOR i IN (
SELECT *
FROM emp
WHERE deptno = T
) LOOP
dbms_output.put_line(i.EName);
END LOOP;
END;

why is my pl/sql code displays output twice?

I have two tables employee and employees
employee
EMPID FIRSTNAME LASTNAME SALARY DEPARTMENT CREDENTIALS
1 ANITHA SHARMA 30000 ASSURANCE
2 EZIO AUDITORE 50000 ASSURANCE
3 CONNOR KENWAY 20000 SUPPORT
4 SAMANTHA JO 25000 ASSURANCE
5 EDWARD MILES 52000 ITIS
employees
EMP_ID EMP_NAME EMP_AGE EMP_SALARY BATHCH
1 charan 20 30000
0 0 0
1 goku 32 1223
2 gokul 24 100000
when i tried to write a pl/sql code to print emp_name,firstname,salary from the above two tables, it prints the records twice.The pl/sql code is
declare
cursor c1 is select * from employee,employees where employee.salary=employees.emp_salary;
c2 c1%rowtype;
begin
open c1;
loop
fetch c1 into c2;
dbms_output.put_line(c2.firstname||' '||c2.emp_name||' '||c2.salary);
exit when c1%notfound;
end loop;
end;
/
I am getting the output as
ANITHA charan 30000
ANITHA charan 30000
Instead of this,
declare
cursor c1 is select * from employee,employees where employee.salary=employees.emp_salary;
c2 c1%rowtype;
begin
open c1;
loop
fetch c1 into c2;
dbms_output.put_line(c2.firstname||' '||c2.emp_name||' '||c2.salary);
exit when c1%notfound;
end loop;
end;
/
Use this,
declare
cursor c1 is select * from employee,employees where employee.salary=employees.emp_salary;
c2 c1%rowtype;
begin
open c1;
loop
fetch c1 into c2;
exit when c1%notfound;
dbms_output.put_line(c2.firstname||' '||c2.emp_name||' '||c2.salary);
end loop;
end;
/
Move the exit when clause after fetch. In your case, the dbms_output.put_line is getting executed twice. For the second iteration, c2 would still hold previous values even when not found condition is true.
You should always use exit when after fetch unless you have a requirement not to do so.
declare
cursor c1 is select * from employee,employees where employee.salary=employees.emp_salary;
c2 c1%rowtype;
begin
open c1;
loop
fetch c1 into c2;
exit when c1%notfound;
dbms_output.put_line(c2.firstname||' '||c2.emp_name||' '||c2.salary);
end loop;
end;
/

Dynamic parameters not sql types Oracle

im trying to do something like this:
create function getData(r1 IN TABLE1%ROWTYPE,col1 string, valor OUT string)RETURN string AS
instruccion VARCHAR2(500);
valor VARCHAR(200);
BEGIN
valor := r1.col1;
return valor;
END getData;
A procedure that takes as parameters a record and the name of the column and give back the value and:
CREATE procedure p1 as
vData VARCHAR2(80);
v1 VARCHAR2(80);
vValue VARCHAR2(80);
inst VARCHAR2(500);
CURSOR vTable2 IS SELECT * FROM TABLE2;
CURSOR vTable1 IS SELECT * FROM TABLE1;
BEGIN
--A cursor for the table with the data
FOR d1 IN vTable1
LOOP
--A cursor for the table that store the name of the columns of the table 1
FOR v1 IN vTable2
LOOP
--get the table1 column name
vData := v1.table2Col1;
--calls the procedure that gives back the value of that record on the column name is stored in VData
inst := 'begin getData(:d1, :vData, :vValue ); end;';
EXECUTE IMMEDIATE inst USING in d1 , vData, OUT vValue;
DBMS_OUTPUT.PUT_LINE(vValue);
END LOOP;
END LOOP;
END p1;
So in summary i just want to have a table that store the names of the table1 in table2, because I want the procedure to work with any table with the name table1, this code gives me the error "expression have to be of SQL types" for the parameters of the dynamic block and i understand it but can figure out a new way to do it. I have tried a lot of things but I'm kind of new in oracle and I really would appreciated the help.
inst := 'begin getData(:d1, :vData, :vValue ); end;';
EXECUTE IMMEDIATE inst USING in d1 , vData, OUT vValue;
I don't see any reason you need to use dynamic SQL here. All you want is to to pass %ROWTYPE parameter and process it. %ROWTYPE is treated as a RECORD, which you would be able to use in the PL/SQL program.
For example,
SQL> CREATE OR REPLACE
2 FUNCTION func(
3 i_dept IN NUMBER,
4 i_emp_rec IN emp%rowtype )
5 RETURN VARCHAR2
6 IS
7 BEGIN
8 RETURN 'DEPTNO = ' || i_dept || ' ENAME = ' || i_emp_rec.ename;
9 END;
10 /
Function created.
SQL>
SQL> SET serveroutput ON
SQL> DECLARE
2 v_deptno dept.deptno%type;
3 cur sys_refcursor;
4 v_ecur sys_refcursor;
5 v_emp emp%rowtype;
6 BEGIN
7 OPEN cur FOR SELECT d.deptno,
8 CURSOR
9 ( SELECT e.* FROM emp e WHERE e.deptno = d.deptno
10 ) e FROM dept d;
11 LOOP
12 FETCH cur INTO v_deptno, v_ecur;
13 EXIT
14 WHEN cur%notfound;
15 LOOP
16 FETCH v_ecur INTO v_emp;
17 EXIT
18 WHEN v_ecur%notfound;
19 dbms_output.put_line(func(v_deptno,v_emp));
20 END LOOP;
21 CLOSE v_ecur;
22 END LOOP;
23 CLOSE cur;
24 END;
25 /
DEPTNO = 10 ENAME = CLARK
DEPTNO = 10 ENAME = KING
DEPTNO = 10 ENAME = MILLER
DEPTNO = 20 ENAME = SMITH
DEPTNO = 20 ENAME = JONES
DEPTNO = 20 ENAME = SCOTT
DEPTNO = 20 ENAME = ADAMS
DEPTNO = 20 ENAME = FORD
DEPTNO = 30 ENAME = ALLEN
DEPTNO = 30 ENAME = WARD
DEPTNO = 30 ENAME = MARTIN
DEPTNO = 30 ENAME = BLAKE
DEPTNO = 30 ENAME = TURNER
DEPTNO = 30 ENAME = JAMES
PL/SQL procedure successfully completed.
SQL>

Resources