Nested Cursor & Recursion in PL/SQL - recursion

I'm new to PL/SQL and have just started working with cursors. I'm writing a small program to print the recursive trace for an employee -> supervisor all the way to the big boss. Essentially my query will output employee, supervisor1, then output supervisor1, supervisor2, all the way to supervisorn, null. My goal is to write a short PL/SQL program that will iterate this process for each employee. My code is as follows:
set serveroutput on;
DECLARE
v_iteration number := 0;
BEGIN
for x in (select * from e)
loop
dbms_output.put_line('recursive trace #' || v_iteration);
v_iteration := v_iteration + 1;
for y in
(WITH sup_hierarch (employee, supervisor) AS
(select x.ssn, x.superssn FROM e
UNION ALL
select supervisor, superssn
from sup_hierarch join e on sup_hierarch.supervisor = e.ssn
where sup_hierarch.supervisor is not null)
select * from sup_hierarch)
loop
dbms_output.put_line(y.employee || ', ' || y.supervisor);
end loop;
end loop;
END;
/
I've used two implicit cursors, the outer implicit cursor is used to loop through the employee table (in order to print the recursive trace for each employee), and the inner implicit cursor is used to loop through the recursive trace itself. The code somewhat works, sample output:
recursive trace #6
666884444, 333445555
666884444, 333445555
666884444, 333445555
666884444, 333445555
666884444, 333445555
666884444, 333445555
666884444, 333445555
666884444, 333445555
333445555, 888665555
333445555, 888665555
333445555, 888665555
333445555, 888665555
333445555, 888665555
333445555, 888665555
333445555, 888665555
333445555, 888665555
888665555,
888665555,
888665555,
888665555,
888665555,
888665555,
888665555,
888665555,
Based on the entirety of my output, the loop through each employee is working correctly. Furthermore, the recursive trace for each employee is working correctly (that is, each employee has correct recursive depth & stack). However, each line in the recursive trace is printing number_of_employee times, when I expected it to be printed once. Why is this?

The problem with your code is the first part of your WITH clause: it returns the same date for as many rows as there are in 'e'.
Why not just do it like this:
DECLARE
v_iteration number := 0; -- useless in the version
BEGIN
for y in
(WITH sup_hierarch (employee, supervisor) AS
(select x.ssn, x.superssn FROM e
UNION ALL
select supervisor, superssn
from sup_hierarch join e on sup_hierarch.supervisor = e.ssn
where sup_hierarch.supervisor is not null)
select * from sup_hierarch)
loop
dbms_output.put_line(y.employee || ', ' || y.supervisor);
end loop;
END;
/
and if you want 2 loops, just to get your iterations:
DECLARE
v_iteration number := 0;
BEGIN
for x in (select * from e)
loop
dbms_output.put_line('recursive trace #' || v_iteration);
v_iteration := v_iteration + 1;
for y in
(WITH sup_hierarch (employee, supervisor) AS
(select x.ssn, x.superssn FROM dual
UNION ALL
select supervisor, superssn
from sup_hierarch join e on sup_hierarch.supervisor = e.ssn
where sup_hierarch.supervisor is not null)
select * from sup_hierarch)
loop
dbms_output.put_line(y.employee || ', ' || y.supervisor);
end loop;
end loop;
END;
/

Related

PL/SQL: I get expression 'I' cannot be used as an assignment target

My code:
create table info(str varchar2(30));
declare
cursor c(job emp_ast.job_id%type, dep emp_ast.department_id%type) is select employee_id
from emp_ast
where job_id=job and department_id=dep;
type t_job is table of emp_ast.job_id%type;
t t_job:=t_job();
emp emp_ast.employee_id%type;
i number(3);
begin
select job_id
bulk collect into t
from emp_ast;
for i in 10..270 loop
for j in 1..t.count loop
open c(i, t(j));
loop
fetch c into emp;
insert into info
values (i||' '||t(j)||' '||emp);
exit when c%notfound;
end loop;
i:=i+10;
end loop;
end loop;
end;
/
I get "expression 'I' cannot be used as an assignment target", reffering to the line where I increment i by 10. I am trying to save the department_id, employee_id and job_id as a string in a table for each department and each job.
At the point where you get that message, i refers to the loop control variable i defined in the line for i in 10..270 loop, not the int(3) variable defined earlier. In PL/SQL a loop definition defines a variable which is only accessible inside the loop, and which you cannot alter. I suggest you change the name of one or the other to make them unique.
EDIT
PL/SQL doesn't provide a way to step by more than 1 in a computed FOR loop. Instead, you will need to compute the desired department number value within the loop:
DECLARE
CURSOR c(job EMP_AST.JOB_ID%TYPE,
dep EMP_AST.DEPARTMENT_ID%TYPE)
IS SELECT EMPLOYEE_ID
FROM EMP_AST
WHERE JOB_ID = job AND
DEPARTMENT_ID = dep;
TYPE t_job IS TABLE OF EMP_AST.JOB_ID%TYPE;
t t_job := t_job();
emp EMP_AST.EMPLOYEE_ID%TYPE;
nDepartment NUMBER;
BEGIN
SELECT job_id
BULK COLLECT INTO t
FROM EMP_AST;
FOR i IN 1..27 LOOP
nDepartment := i * 10;
FOR j IN 1..t.COUNT LOOP
OPEN c(t(j), nDepartment);
LOOP
FETCH c INTO emp;
INSERT INTO info
VALUES (nDepartment || ' ' || t(j) || ' ' || emp);
EXIT WHEN c%notfound;
END LOOP; -- cursor c
CLOSE c;
END LOOP; -- j
END LOOP; -- i
END;
/
Note that in the code above the nDepartment value is computed within the i loop, which now increments from 1 to 27 instead of going from 10 to 270.

PL/SQL: I get the error "Encountered the symbol "OPEN" when expecting one of the following: . ( * # % & - + / "

My code:
create table dep_emp_ast(
cod_dep number(3),
cod_ang number(3));
declare
type cref is ref cursor;
c cref;
type tab_imb is table of dept_ast.department_id%type;
t tab_imb:=tab_imb();
v_ang emp_ast.employee_id%type;
begin
select distinct department_id
bulk collect into t
from dept_ast;
forall i in 1..t.count
open c for select employee_id
from emp_ast
where department_id=t(i);
loop
fetch c into v_ang
insert into dep_emp_ast
values(t(i),v_ang);
exit when c%notfound;
end loop;
close c;
end;
/
My error says I cannot open a cursor there. But why? I want to re-open the cursor and re-use it for every value of t(i).
forall must be followed by a DML statement:
for example:
forall i in depts.first..depts.last
delete employees_temp
where department_id = depts(i);
I think what you wanted was something like:
declare
c sys_refcursor;
type tab_imb is table of dept_ast.department_id%type;
t tab_imb:=tab_imb();
v_ang emp_ast.employee_id%type;
begin
select distinct department_id
bulk collect into t
from dept_ast;
for i in 1..t.count loop
open c for
select employee_id
from emp_ast
where department_id=t(i);
loop
fetch c into v_ang;
insert into dep_emp_ast
values(t(i),v_ang);
exit when c%notfound;
end loop;
close c;
end loop;
end;
which can be simplified to
begin
for d in (
select distinct department_id
from dept_ast
)
loop
for e in (
select employee_id
from emp_ast
where department_id = d.department_id
)
loop
insert into dep_emp_ast
values (d.department_id, e.employee_id);
end loop;
end loop;
end;
which boils down to
insert into dep_emp_ast (cod_dep, cod_ang)
select e.department_id, e.employee_id
from emp_ast e
where e.department_id in
( select department_id
from dept_ast );

Bulk collect and forall with dynamic query

Declare
Vquery varchar2(32000);
Vitem varchar2(50);
Vskuloc varchar2(50);
vstartdate Date;
Vdur Number;
vtype Number;
vqty Float(126);
GP_ohpost Date:= fnc_ohpost;
sdate1 Date:= to_date('01/01/1970','dd/mm/yyyy');
Cursor C_DRIVER is
(Select h.*,b.item,b.skuloc,h.rowid
FROM SCPOMGR.histwide h, SCPOMGR.dfutosku b
WHERE h.dmdunit=b.dmdunit
AND h.loc=b.dfuloc
AND (b.eff = Sdate1 OR b.eff <= h.startdate
AND b.disc = Sdate1 OR b.disc > h.startdate)
And NOT EXISTS (SELECT 1 FROM SCPOMGR.SKUHIST d
WHERE b.dmdunit = d.item
AND b.skuloc = d.loc
AND h.startdate = d.startdate
)) order by h.StartDate;
TYPE GP_cursor_Type IS TABLE OF C_DRIVER%ROWTYPE;
GP_cursor_tab GP_cursor_Type := GP_cursor_type();
c_limit constant PLS_INTEGER DEFAULT 10;
TYPE GP_Insert_type IS TABLE OF scpomgr.skuhist%ROWTYPE;
GP_Insert_tab GP_Insert_type := GP_Insert_type();
GP_tot_accept_fetched NUMBER := 0;
begin
OPEN C_DRIVER;
LOOP
FETCH c_driver BULK COLLECT INTO GP_cursor_tab limit c_limit;
Exit when c_driver%NOTFOUND;
FOR i IN GP_cursor_tab.FIRST .. GP_cursor_tab.LAST LOOP
vquery:= 'Select ...<skipped to make post shortest>';
Execute immediate vquery BULK COLLECT INTO GP_Insert_tab;
FORALL i IN INDICES OF GP_Insert_tab
Insert into scpomgr.skuhist
values( GP_Insert_tab(i).startdate
,1
,10080
,GP_Insert_tab(i).qty
,GP_Insert_tab(i).item
,GP_Insert_tab(i).loc
);
End Loop;
End Loop;
Close C_DRIVER;
END;
/
What i want to do here is i want to use the forall query outside of loop but if i am using the forall with the 2 nd array outside the loop then.all of the records are not getting inserted to the final table..please suggest me some solution....
Ankita.
I didn't know the structure of your tables, so i made a simple tables to understand what should work here:
create table histwide (f1 number);
insert into histwide values (1);
insert into histwide values (2);
create table skuhist (f1 number);
select * from histwide
select * from skuhist
Now i changed your code keeping changed code in comments. Seems it works. I think the trouble was because of you are used Exit when c_driver%NOTFOUND; in begining of loop. It is wrong for FORALL because after first fetching cursor is empty (if limit is reached).
So, my solution of your case:
Declare
Vquery varchar2(32000);
Vitem varchar2(50);
Vskuloc varchar2(50);
vstartdate Date;
Vdur Number;
vtype Number;
vqty Float(126);
GP_ohpost Date:= trunc(sysdate); --fnc_ohpost;
sdate1 Date:= to_date('01/01/1970','dd/mm/yyyy');
Cursor C_DRIVER is
(Select h.*--,b.item,b.skuloc,h.rowid
--FROM SCPOMGR.histwide h, SCPOMGR.dfutosku b
FROM histwide h
/*WHERE h.dmdunit=b.dmdunit
AND h.loc=b.dfuloc
AND (b.eff = Sdate1 OR b.eff <= h.startdate
AND b.disc = Sdate1 OR b.disc > h.startdate)
And NOT EXISTS (SELECT 1 FROM SCPOMGR.SKUHIST d
WHERE b.dmdunit = d.item
AND b.skuloc = d.loc
AND h.startdate = d.startdate
)) order by h.StartDate*/
);
TYPE GP_cursor_Type IS TABLE OF C_DRIVER%ROWTYPE;
GP_cursor_tab GP_cursor_Type := GP_cursor_type();
c_limit constant PLS_INTEGER DEFAULT 10;
TYPE GP_Insert_type IS TABLE OF skuhist%ROWTYPE; --scpomgr.skuhist%ROWTYPE;
GP_Insert_tab GP_Insert_type := GP_Insert_type();
GP_tot_accept_fetched NUMBER := 0;
begin
OPEN C_DRIVER;
LOOP
FETCH c_driver BULK COLLECT INTO GP_cursor_tab limit c_limit;
Exit when GP_cursor_tab.count = 0;
dbms_output.put_line('arr cur size: '||GP_cursor_tab.count);
FOR i IN GP_cursor_tab.FIRST .. GP_cursor_tab.LAST LOOP
vquery:= 'Select * from histwide';
Execute immediate vquery BULK COLLECT INTO GP_Insert_tab;
dbms_output.put_line('arr size: '||GP_Insert_tab.count);
-- FORALL j IN INDICES OF GP_Insert_tab
FORALL j in GP_Insert_tab.first..GP_Insert_tab.last --works too
Insert into skuhist --scpomgr.skuhist
values(GP_Insert_tab(j).f1
/*GP_Insert_tab(i).startdate
,1
,10080
,GP_Insert_tab(i).qty
,GP_Insert_tab(i).item
,GP_Insert_tab(i).loc*/
);
End Loop;
Exit when c_driver%NOTFOUND; --it should be at end of the loop.
End Loop;
Close C_DRIVER;
END;
/
Hope it will help you

USING IF statement inside the CASE in P/L SQL ?

DECLARE
H EMP.EMPNO%TYPE;
F EMP.ENAME%TYPE;
D NUMBER;
S VARCHAR(20);
BEGIN
H := :ENTER_A_DEPTNO;
SELECT ENAME, EMPNO INTO F , D
FROM EMP
WHERE EMPNO = H;
CASE
WHEN D = H THEN
S := F;
WHEN D != H THEN
S := ' NO DATA !!';
END CASE;
DBMS_OUTPUT.PUT_LINE(S);
END;
when i put any number ID not from the table it's not working with the second if statement . I'm wondering how to display the message ?
ORA-01403: no data found
You can use the exception block:
DECLARE
H EMP.EMPNO%TYPE;
F EMP.ENAME%TYPE;
BEGIN
H := :ENTER_A_DEPTNO;
SELECT ENAME INTO F
FROM EMP
WHERE EMPNO = H;
DBMS_OUTPUT.PUT_LINE(F);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE(' NO DATA !!');
END;
There is no need for an if or case, because when the value was found, you can be sure the result concerns the correct employee number. If not found, the exception is raised, and execution is diverted to the EXCEPTION block.

a plsql code increment the counter per 100 record using exit when condition

here is a table having 2000 records i need to update the table with some given condition
update tablename
set counter=1
where counter=null and
rownum<500
for first execution so that counter should be 4 at last of 2000 rows execn,
EXIT WHEN counter > 500; -- LOOP until condition is met
loop will execute some sql
update tablename
set counter=1
where counter=null and
rownum<500
the script will have to run until the conditions of that update state meant are met , I want to run a function that does that automatically for me until the condition is complete(condition is update statement)
With a cursor and 2 counters.
You can add the update code somewhere.
DECLARE
CURSOR MY_CURSOR IS SELECT rowid, object_name FROM <MYTABLE> ;
COUNTER NUMBER :=0;
COUNTER_GROUP NUMBER :=0;
begin
FOR MYCUR IN MY_CURSOR LOOP
EXIT WHEN COUNTER_GROUP > 10 ;
BEGIN
DBMS_OUTPUT.PUT_LINE(MYCUR.object_name || ' COUNTER ' || COUNTER || ' COUNTER_GROUP ' || COUNTER_GROUP);
COUNTER := COUNTER + 1 ;
SELECT floor (( COUNTER - 1 )/500) + 1 INTO COUNTER_GROUP FROM dual;
UPDATE <MYTABLE> SET counter = COUNTER_GROUP WHERE ROWID = MYCUR.rowid ;
EXCEPTION WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('ERROR => ' || SQLERRM);
END;
END LOOP ;
END;
/
DECLARE
CURSOR c1 IS
SELECT * FROM all_objects ;
counter INT:=0;
TYPE c1_typ IS TABLE OF c1%ROWTYPE;
c1_tbl c1_typ;
BEGIN
OPEN c1;
LOOP
FETCH c1 BULK COLLECT INTO c1_tbl LIMIT 200;
counter := counter+1;
EXIT WHEN counter>10;
dbms_output.put_line(200*COUNTER ||' RECORDS GOT PROCESSED .COUNTER :'||counter);
END LOOP;
END;

Resources