I have the following code:
create or replace type t_gib force as object (
fld number,
member function f return number
) not final;
/
declare
type tab_bin is table of pls_integer index by pls_integer;
type t_rec is record (
f1 t_gib
,f2 tab_bin
,f3 tab_bin
,f4 t_gib
);
--
l_rec t_rec;
begin
l_rec.f2(1) := 5;
l_rec.f2(2) := 7;
l_rec.f3(1) := 9;
--
dbms_output.put_line(l_rec.f2.count || '-' || l_rec.f3.count);
dbms_output.put_line(l_rec.f2(l_rec.f2.last) || '-' || l_rec.f3(l_rec.f3.last));
end;
/
This does not compile. But it compiles if I remove the not final.
Additionally it also compiles if I just re-arrange the record thus
type t_rec is record (
f2 tab_bin
,f3 tab_bin
,f4 t_gib
,f1 t_gib
);
Does anybody have an explanation for this behaviour?
Most likely your have some other business going on there as your example works fine (11gR2). Please see the full working example below. I guess you should also drop force and not final keywords unless you know what you're doing, but they should have no effect in this example.
Test type
create or replace type foo_t force is object (
m_f number
,member function f return number
) not final;
/
show errors
create or replace type body foo_t is
member function f return number is
begin
return m_f;
end;
end;
/
show errors
Example use
declare
type int_int_hash_t is table of pls_integer index by pls_integer;
type bar_t is record (
b1 foo_t
,b2 int_int_hash_t
,b3 int_int_hash_t
,b4 foo_t
);
v_bar bar_t;
begin
v_bar.b1 := foo_t(1);
v_bar.b2(2) := 2;
v_bar.b3(3) := 3;
v_bar.b4 := foo_t(4);
dbms_output.put_line(v_bar.b1.f);
dbms_output.put_line(v_bar.b2(2));
dbms_output.put_line(v_bar.b3(3));
dbms_output.put_line(v_bar.b4.f);
end;
/
Results
SQL> #so62.sql
No errors.
1
2
3
4
PL/SQL procedure successfully completed.
SQL>
Related
This is my first question on StackOverflow and I'm self taught so please be gentle.
My goal here is to be able to bulk collect headers/values from a dynamic query/cursor in a generated package via
SELECT *
BULK COLLECT INTO l_cur_val
FROM TABLE (CUSTOM.GET_REF_VAL(l_cursor));
I have this working successfully for column headers in a similar block of code to GET_REF_VAL (simply RETURN l_col_head before entering the /* COLUMN VALUES */ section).
The errors are coming when I'm trying to assign the return value of DBMS_SQL.COLUMN_VALUE into my t_col_val UDT. (Type definitions are in comments)
TYPES
CREATE OR REPLACE TYPE CUSTOM.r_col_val IS OBJECT (l_col_val VARCHAR2(250 byte));
CREATE OR REPLACE TYPE CUSTOM.t_col_val IS TABLE OF CUSTOM.r_col_val;
ERRORS
--l_val(n) := r_col_val(l_dum_val); --returns: ORA-06533: Subscript beyond count
--l_val(n) := l_dum_val; --returns: PLS-00382: expression is of wrong type
Table return function GET_REF_VAL
CREATE OR REPLACE FUNCTION
CUSTOM.GET_REF_VAL
(
p_cursor IN SYS_REFCURSOR
)
RETURN t_col_val
IS
l_val t_col_val := t_col_val();
l_col t_col_head := t_col_head();
n INTEGER := 0;
l_cursor SYS_REFCURSOR := p_cursor;
l_cursor_id INTEGER;
l_dummy INTEGER;
l_col_cnt INTEGER;
l_tab_rec DBMS_SQL.DESC_TAB2;
l_dum_val VARCHAR2(250);
BEGIN
l_cursor_id := DBMS_SQL.TO_CURSOR_NUMBER(l_cursor);
DBMS_SQL.DESCRIBE_COLUMNS2(l_cursor_id, l_col_cnt, l_tab_rec);
/* COLUMN HEADERS */
FOR r IN 1..l_col_cnt
LOOP
l_col.extend;
n := n + 1;
l_col(n) := r_col_head(l_tab_rec(r).col_name);
DBMS_SQL.DEFINE_COLUMN(l_cursor_id, r, l_dum_val, 4000);
END LOOP;
/* COLUMN VALUES */
LOOP
IF DBMS_SQL.FETCH_ROWS(l_cursor_id)> 0 THEN
FOR i IN 1 .. l_col_cnt
LOOP
l_val.extend;
n := n + 1;
DBMS_SQL.COLUMN_VALUE(l_cursor_id, i, l_dum_val);
DBMS_OUTPUT.PUT_LINE(l_dum_val); -- This return l_dum_val with no issues
--l_val(n) := r_col_val(l_dum_val); -- ORA-06533: Subscript beyond count
--l_val(n) := l_dum_val; --PLS-00382: expression is of wrong type
END LOOP;
ELSE
EXIT;
END IF;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(l_cursor_id);
RETURN l_val;
END;
/
Execution block
DECLARE
l_sql_stmt VARCHAR(10000) :=
q'!
SELECT
SYS_CONTEXT('USERENV','OS_USER') AS OS_USER ,
SYS_CONTEXT('USERENV','SESSION_USER') AS SESSION_USER,
SYS_CONTEXT('USERENV','ISDBA') AS ISDBA,
SYS_CONTEXT('USERENV','SID') AS SID,
SYS_CONTEXT('USERENV','CURRENT_SQL') AS CURRENT_SQL,
SYS_CONTEXT('USERENV','DB_NAME') AS DB_NAME,
SYS_CONTEXT('USERENV','HOST') AS HOST,
SYS_CONTEXT('USERENV','IP_ADDRESS') AS IP_ADDRESS,
SYS_CONTEXT('USERENV','SERVICE_NAME') AS SERVICE_NAME
FROM
DUAL
!';
l_cursor SYS_REFCURSOR;
l_cursor_id INTEGER;
l_dummy VARCHAR2(50);
TYPE t_cur_head IS TABLE OF VARCHAR2(250) INDEX BY BINARY_INTEGER;
l_cur_head t_cur_head;
TYPE t_cur_val IS TABLE OF VARCHAR2(250) INDEX BY BINARY_INTEGER;
l_cur_val t_cur_val;
BEGIN
l_cursor := CUSTOM.GET_REF_CUR(l_sql_stmt);
IF l_cursor%ISOPEN
THEN
/* Header fetch works fine */
/*
SELECT *
BULK COLLECT INTO l_cur_head
FROM TABLE (CUSTOM.GET_REF_HEAD(l_cursor));
FOR i IN 1 .. l_cur_head.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE(l_cur_head(i));
END LOOP;
*/
/* Values fetch fails */
SELECT *
BULK COLLECT INTO l_cur_val
FROM TABLE (CUSTOM.GET_REF_VAL(l_cursor));
FOR i IN 1 .. l_cur_val.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE(l_cur_val(i));
END LOOP;
END IF;
END;
So I guess in summary what I want to know is
a) How to handle the return value of dbms_sql.column_value using a user defined type
b) How insert a VARCHAR2 value (l_dum_val) into a UDT object with VARCHAR2 records (l_col_val)
c) Any other obvious errors/bad practices in the code?
Thank you for your time an patience.
The first of your commented-out lines:
--l_val(n) := r_col_val(l_dum_val); -- ORA-06533: Subscript beyond count
gets that error because you are not resetting n to zero before the second loop. You don't really need that counter variable at all though, you can do use l_val.count instead (in both loops).
The second of your commented-out lines:
--l_val(n) := l_dum_val; --PLS-00382: expression is of wrong type
gets that error because the l_val(n) is pointing to an object, which has a string attribute; it isn't pointing directly to a string. So you can assign a new object via its constructor; which is what the first version was trying to do, but it should be:
l_val(l_val.count) := r_col_val(l_dum_val);
Once that object exists you can assign the attribute directly with:
l_val(some_index).l_col_val := r_col_val(l_dum_val);
but you have to create an object before you can access its attributes, and as you only have a default constructor, that probably isn't going to be much use to you in this case.
So with those changes (and some indentation, and refactoring slightly to get rid of the else) this now works:
CREATE OR REPLACE FUNCTION
GET_REF_VAL
(
p_cursor IN SYS_REFCURSOR
)
RETURN t_col_val
IS
l_val t_col_val := t_col_val();
l_col t_col_head := t_col_head();
l_cursor SYS_REFCURSOR := p_cursor;
l_cursor_id INTEGER;
l_dummy INTEGER;
l_col_cnt INTEGER;
l_tab_rec DBMS_SQL.DESC_TAB2;
l_dum_val VARCHAR2(250);
BEGIN
l_cursor_id := DBMS_SQL.TO_CURSOR_NUMBER(l_cursor);
DBMS_SQL.DESCRIBE_COLUMNS2(l_cursor_id, l_col_cnt, l_tab_rec);
/* COLUMN HEADERS */
FOR r IN 1..l_col_cnt
LOOP
l_col.extend;
l_col(l_col.count) := r_col_head(l_tab_rec(r).col_name);
DBMS_SQL.DEFINE_COLUMN(l_cursor_id, r, l_dum_val, 4000);
END LOOP;
/* COLUMN VALUES */
LOOP
IF DBMS_SQL.FETCH_ROWS(l_cursor_id) = 0 THEN
EXIT;
END IF;
FOR i IN 1 .. l_col_cnt
LOOP
l_val.extend;
DBMS_SQL.COLUMN_VALUE(l_cursor_id, i, l_dum_val);
DBMS_OUTPUT.PUT_LINE(l_dum_val);
l_val(l_val.count) := r_col_val(l_dum_val);
END LOOP;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(l_cursor_id);
RETURN l_val;
END;
/
db<>fiddle
Your code suggests you have a separate function to get the headers, so you're duplicating code. You could simplify into one procedure with two out variables instead:
CREATE OR REPLACE PROCEDURE
GET_REF_HEAD_AND_VAL
(
p_cursor IN OUT SYS_REFCURSOR,
p_col OUT SYS.odcivarchar2list,
p_val OUT SYS.odcivarchar2list
)
IS
l_cursor_id INTEGER;
l_col_cnt INTEGER;
l_tab_rec DBMS_SQL.DESC_TAB3;
l_value VARCHAR2(250 byte);
BEGIN
l_cursor_id := DBMS_SQL.TO_CURSOR_NUMBER(p_cursor);
DBMS_SQL.DESCRIBE_COLUMNS3(l_cursor_id, l_col_cnt, l_tab_rec);
/* COLUMN HEADERS */
p_col := SYS.odcivarchar2list();
FOR r IN 1..l_col_cnt
LOOP
p_col.extend;
p_col(p_col.count) := l_tab_rec(r).col_name;
DBMS_SQL.DEFINE_COLUMN(l_cursor_id, r, l_value, 250);
END LOOP;
/* COLUMN VALUES */
p_val := SYS.odcivarchar2list();
LOOP
IF DBMS_SQL.FETCH_ROWS(l_cursor_id) = 0 THEN
EXIT;
END IF;
FOR i IN 1 .. l_col_cnt
LOOP
p_val.extend;
DBMS_SQL.COLUMN_VALUE(l_cursor_id, i, l_value);
--DBMS_OUTPUT.PUT_LINE(l_dum_val);
p_val(p_val.count) := l_value;
END LOOP;
END LOOP;
DBMS_SQL.CLOSE_CURSOR(l_cursor_id);
END;
/
This is using a built-in collection type rather than creating your own object/table types (though you could still create your own collection type; it doesn't need to used objects though).
db<>fiddle
CREATE PROCEDURE book_check(book_Id varchar(64))
begin
declare book_available varchar(64);
select book_id into book_available
from book_copies
where No_of_Copies >0 and book_id=book_Id;
if(book_Id in book_available ) then
select concat ("Book available");
else
select concat ("Book not available");
end if;
end
//
what can i write in place of 'in' . I know the syntax i wrong .
It's easy - try something like this:
create or replace function book_check(book_id varchar) return varchar as
begin
for r in (select 1 from book_copies where no_of_copies > 0 and book_id = book_check.book_id) loop
return 'Book available';
end loop;
return 'Book not available';
end book_check;
/
It's unclear to me what you are trying to do. I assume you want to find out if a book is available or not and return that information to the caller of the function.
Your declaration of the procedure header and the variables is wrong.
Procedure or function parameters are not defined with a length for the datatype.
Inside a procedure or function you don't need declare
you can't have a select statement without putting the result somewhere. * Assigning a constant value to a variable is done using :=
If you want to return information to the caller, use a function, not a procedure
You should not give variables or parameters the same name as a column. A common naming convention in the Oracle world is to give parameters the prefix p_ and local variables the prefix l_ but anything that avoids a name clash between column names and variables is OK - just be consistent.
CREATE function book_check(p_book_id varchar)
return varchar
as
l_count integer;
l_result varchar(20);
begin
select count(*)
into l_count
from book_copies
where No_of_Copies > 0
and book_id = p_book_id;
if l_count > 0 then
l_result := 'Book available';
else
l_result := "Book not available";
end if;
return result;
end;
/
You should really take the time and read the PL/SQL Language reference. All the above is explained there.
I have a list of comma-separated strings (from a user input) and I'd like to use this list as a parameter in a pl/sql stored function in a nested sql block using a "not in where clause".
I can't find an elegant way to make it work...
That's what I'm thinking of:
CREATE TABLE example ( somevalue VARCHAR(36) NOT NULL);
--
INSERT INTO example VALUES ('value1');
INSERT INTO example VALUES ('value2');
INSERT INTO example VALUES ('value3');
--
SELECT * FROM example;
--
CREATE OR REPLACE
FUNCTION resultmaker(
ignoreList IN VARCHAR2)
RETURN VARCHAR2
IS
result VARCHAR2(4000);
BEGIN
result := 'Here is my calculated result, using ignorelist=' || ignoreList || ':' || CHR(10);
FOR rec IN
(SELECT DISTINCT somevalue
FROM example
WHERE somevalue NOT IN resultmaker.ignoreList -- here's my issue, the NOT IN clause using the parameter value
)
LOOP
result := result || 'not in ignorelist: ' || rec.somevalue || CHR(10);
END LOOP;
result := result || '.' || CHR(10);
--
RETURN result;
END resultmaker;
/
--
-- simulate function call with user input 'value2, value3'
SELECT resultmaker('value2, value3') FROM dual; -- doesn't work
--
DROP TABLE example;
DROP FUNCTION resultmaker;
Just pass the parameter like '"value2","value3"' and have your statement replace the double quote with single quotes like REPLACE(#Param1,'"','''').
Call to function: SELECT * FROM Function1('"value2","value3"')
Inside function: NOT IN REPLACE(#Param1,'"','''')
In every case you should parse that input. As there is no built-in string tokenizer in PL/SQL (at least I couldn't find it) You may want to look into these options,
http://blog.tanelpoder.com/2007/06/20/my-version-of-sql-string-to-table-tokenizer/
Does PL/SQL have an equivalent StringTokenizer to Java's?
After you parsed the string, you may create a new string like:
not_in_statement varchar2(1000);
CURSOR c1 IS select token from tokenized_strings_table;
BEGIN
not_in_statement := '('
FOR rec IN c1 LOOP
not_in_statement := not_in_statement || '''||rec.token||'''||','
END LOOP
not_in_statement := not_in_statement||')'
END
SELECT DISTINCT somevalue
FROM example
WHERE somevalue NOT IN not_in_statement
You may need to make it dynamic SQL, I did not have time to try.
Here's my solution using dynamic sql for my original question above:
CREATE TABLE example ( somevalue VARCHAR(36) NOT NULL);
--
INSERT INTO example VALUES ('value1');
INSERT INTO example VALUES ('value2');
INSERT INTO example VALUES ('value3');
--
SELECT * FROM example;
--
CREATE OR REPLACE
FUNCTION resultmaker(
ignoreList IN VARCHAR2)
RETURN VARCHAR2
IS
result VARCHAR2(4000);
example_cursor sys_refcursor;
rec example.somevalue%type;
BEGIN
result := 'Here is my calculated result, using ignorelist=' || ignoreList || ':' || CHR(10);
OPEN example_cursor FOR ( 'SELECT DISTINCT somevalue FROM example WHERE somevalue NOT IN (' || ignoreList || ')' );
FETCH example_cursor INTO rec;
WHILE example_cursor%found
LOOP
result := result || 'not in ignorelist: ' || rec || CHR(10);
FETCH example_cursor INTO rec;
END LOOP;
CLOSE example_cursor;
result := result || '.' || CHR(10);
--
RETURN result;
END resultmaker;
/
--
-- simulate function call with user input 'value2', 'value3'
SELECT resultmaker('''value2'', ''value3''') FROM dual;
--
DROP TABLE example;
DROP FUNCTION resultmaker;
The classic and probably correct solution would be to use PL/SQL table passing it as prameter...
There are some good solutions at asktom.oracle.com regarding taking a string of values and dynamically creating an IN clause for them:
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:210612357425
What is the diffecence between adding INDEX BY PLS_INTEGER and not at end of declaration of new table type. Look at this example:
DECLARE
GC_BULK_LIMIT CONSTANT INTEGER := 500;
CURSOR CUR_CLIENTS IS SELECT C.ID, C.NAME FROM CLIENTS C;
TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE;
-- TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE INDEX BY PLS_INTEGER;
LT_CLIENTS RT_CLIENTS;
BEGIN
OPEN CUR_CLIENTS;
LOOP
FETCH CUR_CLIENTS BULK COLLECT INTO LT_CLIENTS LIMIT GC_BULK_LIMIT;
EXIT WHEN LT_CLIENTS.COUNT = 0;
FOR I IN 1..LT_CLIENTS.COUNT LOOP
-- ... SOME LOGIC
END LOOP;
END LOOP;
CLOSE CUR_CLIENTS;
END;
in response to "must i add". The short answer is NO.
the difference is that
TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE;
Is a nested table. This means that for a given variable of this type, we know that the subscripts are sequential. i.e. the subscript starts from 1 and goes up to the array length.
The following loop therefore, is the right way to access a nested table array:
FOR I IN 1..LT_CLIENTS.COUNT LOOP
This however, is called a associative array:
TYPE RT_CLIENTS IS TABLE OF CUR_CLIENTS%ROWTYPE INDEX BY PLS_INTEGER;
(you could also index by a varchar2 if you wanted). The difference is that the subscripts in this case do not have to be sequential, depending on how the array was populated. In your code, they would be (as bulk collect would do that), but its not always the case.
The safe way to access and loop through an index by array is :
v_subscript := t_arr.first;
while v_subscript is not null loop
dbms_output.put_line(v_subscript || ': ' || t_arr(v_subscript));
v_subscript := t_arr.next(v_subscript);
end loop;
where v_subscript is a variable of the same datatype of the index by part.
also with a nested table, you can populate the array quickly with:
declare
type myarr is table of number;
t_arr myarr;
v_subscript number;
begin
t_arr := myarr(1, 12, 44);
whereas with an index by array you'd have to have three lines there to populate it:
t_arr(1):= 1;
t_arr(2):= 12;
t_arr(3):= 44;
for your particular case, without the index by is perfectly fine.
further reading: http://docs.oracle.com/cd/E18283_01/appdev.112/e17126/composites.htm
I am trying to figure out how loop through a hierarchy, I don't know how to put in PLSQL. What I am trying to achieve: I want to know what department is 10 steps above me in a hierarchy:
Say I have a table with a department and a parent department. I want to perform this kind of operation:
select my_department from table_departments as v_department
FOR counter in 1...9
LOOP
v_department:=
(SELECT parent_department
FROM table_department_hierarchy
WHERE child_department=v_department)
END LOOP as top_department;
I can't figure out the correct syntax, is there a brave soul out there who can help me?
Your method with corrected PL/SQL syntax would be something like:
begin
select my_department into v_department from table_departments;
FOR counter in 1...9
LOOP
SELECT parent_department
INTO v_department
FROM table_department_hierarchy
WHERE child_department=v_department;
END LOOP:
END;
However you could perhaps get it all in one statement something like this:
SELECT parent_department
INTO v_department
FROM
( SELECT parent_department, level as lvl
FROM table_department_hierarchy
CONNECT BY child_department = PRIOR parent_department
START WITH child_department = v_department
)
WHERE lvl = 9;
See Oracle docs on hierarchical queries
This is a large pl/sql procedure that i wrote a long while ago that was meant to traverse a employee/boss reporting tree all the way to the top (CEO). This version was specific to Peoplesoft but it as long as your reading something that has a parent/child relationship in a record it will work on anything.... I have other more dynamic versions of this but this maybe the simplest to decipher. I removed some fluff stuff that you won't care about. Also this particular solution delivers to a table for many different reasons because reporting tools can consume it...
Also it determines levels dynamically so you don't have to know how many levels there are as you would with a connect by solution.
Hope it helps:
CREATE OR REPLACE PROCEDURE DW."SPW_T_RESOURCE_HIERARCHY" (iCommit In Integer Default 1000,
pdBegin In Date Default trunc(Sysdate) - 3,
pdEnd In Date Default trunc(Sysdate) + 1,
vTruncate In Varchar2 Default 'Y' ) Is
------------------------------------------------------
-- DECLARATIONS
------------------------------------------------------
Cursor curDataSource Is
---**********************************----
---****BEGIN CUSTOMIZE THIS BLOCK****----
---**********************************----
Select
Eh.empl_id F_DESCENDANT_ID
,Eh.Super_Empl_Id F_IMMEDIATE_ANCESTOR_ID
From
Employee_Header EH
Where
EH.SUPER_EMPL_ID IS NOT NULL OR EH.TERM_DATE IS NULL;
---**********************************----
---****END CUSTOMIZE THIS BLOCK******----
---**********************************----
dNow Date := Sysdate;
iTotalRows Integer := 0;
iTotalErrors Integer := 0;
---**********************************----
---****BEGIN CUSTOMIZE THIS BLOCK****----
---**********************************----
vDescendentID Varchar2(20);
iDescendentLevel Integer := 1;
iAncestorLevel Integer := 0;
vAncestorID Varchar2(20);
vTmpAncestorID Varchar2(20);
vTmpEmployeeID Varchar2(20);
---**********************************----
---****END CUSTOMIZE THIS BLOCK******----
---**********************************----
------------------------------------------------------
-- END DECLARATIONS
------------------------------------------------------
------------------------------------------------------
-- BEGIN MAIN
------------------------------------------------------
Begin
-- Loop over source records
For recDataSource In curDataSource
Loop
iDescendentLevel := 1;
vAncestorID := recDataSource.f_Immediate_Ancestor_Id;
-- Start Transaction
Begin
while (Trim(vAncestorID) is not null)
loop
Begin
-- Fetch Next Ancestor
Select EH.SUPER_EMPL_ID
Into vTmpAncestorID
From
EMPLOYEE_HEADER EH
Where
EH.EMPL_ID = vAncestorID;
Exception
When Others Then
vTmpAncestorID := null;
End;
If NVL(vTmpAncestorID,'-XYZ-') = NVL(vAncestorID,'-123-') Then
vTmpAncestorID := null;
End If;
vAncestorID := vTmpAncestorID;
iDescendentLevel := iDescendentLevel + 1;
end loop;
-- Insert Resource Base
Insert Into T_RESOURCE_HIERARCHY
(
T_RESOURCE_HIERARCHY.F_HIERARCHY_NAME,
T_RESOURCE_HIERARCHY.F_DESCENDANT_LEVEL,
T_RESOURCE_HIERARCHY.F_DESCENDANT_ID,
T_RESOURCE_HIERARCHY.F_ANCESTOR_LEVEL,
T_RESOURCE_HIERARCHY.F_ANCESTOR_ID
)
Values
(
'Physical Org Chart',
iDescendentLevel,
recDataSource.f_Descendant_Id,
To_Number(Decode(iDescendentLevel,1,2,iDescendentLevel) - 1),
NVL(recDataSource.f_Immediate_Ancestor_Id,'ROOT')
);
-- Insert MySelf Into Resource Base as well for full hierarchy research
Insert Into T_RESOURCE_HIERARCHY
(
T_RESOURCE_HIERARCHY.F_HIERARCHY_NAME,
T_RESOURCE_HIERARCHY.F_DESCENDANT_LEVEL,
T_RESOURCE_HIERARCHY.F_DESCENDANT_ID,
T_RESOURCE_HIERARCHY.F_ANCESTOR_LEVEL,
T_RESOURCE_HIERARCHY.F_ANCESTOR_ID
)
Values
(
'Physical Org Chart',
iDescendentLevel,
recDataSource.f_Descendant_Id,
iDescendentLevel,
NVL(recDataSource.f_Descendant_Id,'ROOT')
);
-- Now Its Time To Climb The Tree
-- For This Employee
vAncestorID := recDataSource.f_Immediate_Ancestor_Id;
iAncestorLevel := iDescendentLevel-1;
vTmpAncestorID := null;
-- Loop over parents
while (Trim(vAncestorID) is not null)
loop
Begin
-- Fetch Next Ancestor
Select EH.SUPER_EMPL_ID
Into vTmpAncestorID
From
EMPLOYEE_HEADER EH
Where
EH.EMPL_ID = vAncestorID;
Exception
When Others Then
vTmpAncestorID := null;
End;
If NVL(vTmpAncestorID,'-XYZ-') = '-XYZ-' Then
vTmpAncestorID := null;
End If;
vAncestorID := vTmpAncestorID;
iAncestorLevel := iAncestorLevel - 1;
If vAncestorID is not null Then
-- Insert Resource Base
Insert Into T_RESOURCE_HIERARCHY
(
T_RESOURCE_HIERARCHY.F_HIERARCHY_NAME,
T_RESOURCE_HIERARCHY.F_DESCENDANT_LEVEL,
T_RESOURCE_HIERARCHY.F_DESCENDANT_ID,
T_RESOURCE_HIERARCHY.F_ANCESTOR_LEVEL,
T_RESOURCE_HIERARCHY.F_ANCESTOR_ID
)
Values
(
'Physical Org Chart',
iDescendentLevel,
recDataSource.f_Descendant_Id,
iAncestorLevel,
vAncestorID
);
End If;
end loop;
-- TRANSACTION EXCEPTION HANDLING
Exception
When Others Then
End;
-- ASSIGN HOW MANY RECORDS PROCESSED
iTotalRows := curDataSource%Rowcount;
-- CONDITIONAL/INCREMENTAL TRANSACTION COMMIT
If Mod(iTotalRows, iCommit) = 0
Then
Commit;
End If;
End Loop;
-- FINAL COMMIT AND MD UPDATE
Commit;
-- MAIN EXCEPTION HANDLING
Exception
When Others Then
Begin
iExceptionCode := Sqlcode;
vExceptionMessage := Sqlerrm;
Raise_application_error(Sqlcode, Sqlerrm);
End;
------------------------------------------------------
-- END MAIN
------------------------------------------------------
End SPW_T_RESOURCE_HIERARCHY;
/
Please, check the following example. Not tested, but believe :)
DECLARE
G_EMPLOYEE_ID NUMBER:=1880;
FUNCTION GET_MANAGER(V_EMPLOYEE_ID NUMBER) RETURN NUMBER IS
V_MANAGER_ID NUMBER;
BEGIN
SELECT ID_MANAGER INTO V_MANAGER_ID FROM EMPLOYEES WHERE EMPLOYEE_ID = V_EMPLOYEE_ID;
RETURN V_MANAGER_ID;
EXCEPTION
WHEN OTHERS THEN
RETURN NULL;
END;
BEGIN
LOOP
DBMS_OUTPUT.PUT_LINE('EMPLOYEE:' || G_EMPLOYEE_ID);
G_EMPLOYEE_ID := GET_MANAGER(G_EMPLOYEE_ID);
DBMS_OUTPUT.PUT_LINE('MANAGER:' || G_EMPLOYEE_ID);
EXIT WHEN G_EMPLOYEE_ID IS NULL;
END LOOP;
END;
Another great option (primary) is CONNECT BY, START WITH