Convert array of records to refcursor - oracle

The question is how to return the l_array as refcursor,
Since the interface i am using can handle cursor easily rather than an array of record.
Plz help
create or replace package sample
TYPE r_type is record( code number; description varchar2(50));
TYPE tr_type IS TABLE OF r_type; l_rarray tr_type ; ind number:=0;
PROCEDURE getdata() IS
CURSOR cur IS
SELECT empid, empname, place, location FROM emp;
TYPE epmid_aat IS TABLE OF emp.empid%TYPE INDEX BY BINARY_INTEGER;
l_empid empid_aat;
BEGIN
k := 1;
FOR j IN (SELECT DISTINCT empid FROM emp)
LOOP
l_empid(k) := j.empid;
k := k + 1;
END LOOP;
FOR i IN cur
LOOP
FOR k IN l_empid.first .. l_empid.last
LOOP
IF l_empid(k) = i.empid THEN
procedure2(i.emp_id);
END IF;
END LOOP;
END LOOP;
END getdata();
PROCEDURE procedure2
(
empid_in IN NUMBER,
description_in IN VARCHAR2(20)
) IS
BEGIN
lrec.code := empid_in;
lrec.description := description_in;
l_rarray(ind) := lrec;
ind := ind + 1;
END procedure2;
end;

I think it should be like this :
OPEN YourRefCursor
FOR SELECT * FROM TABLE (Cast(l_rarray AS tr_type));

Something like this.
TYPE r_type is record ( code number;
description varchar2(50)
);
TYPE tr_type IS TABLE OF r_type;
l_rarray tr_type ;
SELECT r_type(empid, empname)
BULK COLLECT INTO l_rarray
FROM emp;
OPEN YourRefCursor
SELECT *
FROM TABLE (Cast(l_rarray AS r_type));

Related

Creating a complex ID using a trigger

In many of our applications we use an identity column to generate a unique number, such as a customer_id.
Our internal auditors feel this is a possible breach of security and going forward we want to use something a bit more complex.
I found a function below base34 that I want to pass a concatenation of SYS_Guid, part of a TIMESTAMP and a sequence number to create a more complex ID.
Below is my test CASE. Is there a way I can use the base34 function in a before INSERT trigger with the above concatenation without changing the base34 function to achieve this task.
For example, let's say I have the following table.
CREATE TABLE CUSTOMERS (
customer_id VARCHAR2 (20),
first_name VARCHAR2 (20),
last_name VARCHAR2 (20));
And I want the trigger to populate customer_id
Thanks in advance for your time and expertise.
create table t ( pk number);
create sequence seq start with 1000000 minvalue 1000000 maxvalue 9999999 cycle;
begin
for i in 1 .. 10 loop
insert into t values ( to_number(trunc(dbms_random.value(1000,9999))|| to_char(systimestamp,'FFSS')||
seq.nextval));
end loop;
end;
/
create or replace function base34(p_num number) return varchar2 is
l_dig varchar2(34) := 'AB0CD1EF2GH3JK4LM5NP6QR7ST8UV9WXYZ';
l_num number := p_num;
l_str varchar2(38);
begin
loop
l_str := substr(l_dig,mod(l_num,34)+1,1) || l_str ;
l_num := trunc(l_num/34);
exit when l_num = 0;
end loop;
return l_str;
end;
/
create or replace function dec34(p_str varchar2) return number is
l_dig varchar2(34) := 'AB0CD1EF2GH3JK4LM5NP6QR7ST8UV9WXYZ';
l_num number := 0;
begin
for i in 1 .. length(p_str) loop
l_num := l_num * 34 + instr(l_dig,upper(substr(p_str,i,1)))-1;
end loop;
return l_num;
end;
/
select base34(pk) from t where rownum <= 10;
select to_char(pk) from t where rownum = 1
union all
select base34(pk) from t where rownum = 1
union all
select to_char(dec34(base34(pk))) from t where rownum = 1;
CREATE TABLE CUSTOMERS (
customer_id VARCHAR2 (20),
first_name VARCHAR2 (20),
last_name VARCHAR2 (20));
create sequence seq start with 1000000 minvalue 1000000 maxvalue 9999999 cycle;
create or replace function base34(p_num number) return varchar2 is
l_dig varchar2(34) := 'AB0CD1EF2GH3JK4LM5NP6QR7ST8UV9WXYZ';
l_num number := p_num;
l_str varchar2(38);
begin
loop
l_str := substr(l_dig,mod(l_num,34)+1,1) || l_str ;
l_num := trunc(l_num/34);
exit when l_num = 0;
end loop;
return l_str;
end;
CREATE OR REPLACE TRIGGER customer_trg
BEFORE INSERT
ON customers
FOR EACH ROW
BEGIN
:NEW.customer_id := base34(to_number(trunc(dbms_random.value(1000,9999))|| to_char(systimestamp,'FFSS')||
seq.nextval));
END;
begin
for i in 1 .. 100 loop
INSERT into customers (first_name, last_name) VALUES ('John', 'Doe');
end loop;
end;
/

Oracle PL/SQL : search record type value

I am using Oracle 11G. Lets say I load data into a collection of ROWTYPE.
How can I extract a name or age from the collection emp_tab if I just pass id as the parameter without using any LOOP's?
create table emp (id number, name varchar2(20), age number);
insert into emp values(10,'Tom',20);
insert into emp values(20,'Nicole',30);
commit;
select * from emp;
declare
TYPE emp_t IS TABLE OF emp%ROWTYPE INDEX BY PLS_INTEGER;
emp_tab emp_t;
begin
select id,name,age bulk collect into emp_tab from emp;
for idx in 1..emp_tab.count loop
dbms_output.put_line(emp_tab(idx).name);
end loop;
end;
You'll need to index your collection by emp.id, and to populate it you can't use bulk collect (at least not in 11g - it may be possible in 20c).
declare
type emp_t is table of emp%rowtype index by pls_integer;
emp_tab emp_t;
idx emp.id%type;
begin
for r in (
select id, name, age
from emp
)
loop
emp_tab(r.id).name := r.name;
emp_tab(r.id).age := r.age;
end loop;
idx := emp_tab.first;
while idx is not null loop
dbms_output.put_line(emp_tab(idx).name);
idx := emp_tab.next(idx);
end loop;
end;

Get Specific Row From Table Type With my Filter Values

I have procedure like as below:
cursor my_cursor is
select first_column, second_column, third_column from table_name;
TYPE my_cursor_type is TABLE OF my_cursor_type%ROWTYPE INDEX BY BINARY INTEGER;
my_cur my_cursor_type;
TYPE table_type IS TABLE OF table_name%ROW_TYPE INDEX BY BINARY INTEGER;
table_obj table_type;
begin
open my_cursor;
loop
fetch my_cursor bulk collect
into my_cur limit 5000;
exit when my_cursor%notfound;
for i in 1 .. my_cur.count loop
table_obj(i).first_column := my_cur(i).first_column;
table_obj(i).second_column := my_cur(i).second_column;
table_obj(i).third_column := my_cur(i).third_column;
end loop;
end loop;
Close my_cursor;
……
Now after these codes ı have table_obj which has 100000 record. And this table object has first_column, second_column, third_column.
I search one record's third_column in table_obj. I know first_column, second_column, and i search third_column in table_obj. I must fetch row from table_obj rows. This searched row has my first_column, second_column values.
And i get third_column_value from this row. How can i get specific row from table_obj rows with plsql ?
This can be done by transforming the first two columns to a single index. Concatenation with a separator works best for that. The separator should be a Character that is not used in either of the first two columns.
DECLARE
CURSOR my_cursor
IS
SELECT first_column, second_column, third_column FROM table_name;
TYPE my_cursor_type IS TABLE OF my_cursor_type%ROWTYPE
INDEX BY BINARY_INTEGER;
my_cur my_cursor_type;
TYPE table_type IS TABLE OF table_name%ROWTYPE
INDEX BY BINARY_INTEGER;
table_obj table_type;
l_composite_idx VARCHAR2( 61 );
l_test_idx_1 VARCHAR2( 30 ) := 'Test1';
l_test_idx_2 VARCHAR2( 30 ) := 'Success';
BEGIN
OPEN my_cursor;
LOOP
FETCH my_cursor BULK COLLECT INTO my_cur LIMIT 5000;
FOR i IN 1 .. my_cur.COUNT
LOOP
l_composite_idx := my_cur( i ).first_column || '&' || my_cur( i ).second_column;
table_obj( l_composite_idx ).first_column := my_cur( i ).first_column;
table_obj( l_composite_idx ).second_column := my_cur( i ).second_column;
table_obj( l_composite_idx ).third_column := my_cur( i ).third_column;
END LOOP;
EXIT WHEN my_cursor%NOTFOUND;
END LOOP;
CLOSE my_cursor;
-- You access a row in my_cur like this:
DBMS_OUTPUT.put_line( 'Result: ' || table_obj( l_test_idx_1 || '&' || l_test_idx_2 ) );
END;
Hope this helps!

How to read multiple values at one time as an input to single variable in PLSQL?

Could you help me to pass the input values (at execution time: i mean to enter multiple values for single variable at once).
Here is my code for which i am giving one input at a time either hard coded input or single input at time.
declare
type TEmpRec is record (
EmployeeID EMPLOYEES.EMPLOYEE_ID%TYPE,
LastName EMPLOYEES.LAST_NAME%TYPE
);
type TEmpList is table of TEmpRec;
vEmpList TEmpList;
---------
function EmpRec(pEmployeeID EMPLOYEES.EMPLOYEE_ID%TYPE,
pLastName EMPLOYEES.LAST_NAME%TYPE default null) return TEmpRec is
-- Effective "Record constructor"
vResult TEmpRec;
begin
vResult.EmployeeID := pEmployeeID;
vResult.LastName := pLastName;
return vResult;
end;
---------
procedure SearchRecs(pEmpList in out nocopy TEmpList) is -- Nocopy is a hint to pass by reference (pointer, so small) rather than value (actual contents, so big)
vIndex PLS_integer;
begin
if pEmpList is not null then
vIndex := pEmpList.First;
while vIndex is not null -- The "while" approach can be used on sparse collections (where items have been deleted)
loop
begin
select LAST_NAME
into pEmpList(vIndex).LastName
from EMPLOYEES
where EMPLOYEE_ID = pEmpList(vIndex).EmployeeID;
exception
when NO_DATA_FOUND then
pEmpList(vIndex).LastName := 'F'||pEmpList(vIndex).EmployeeID;
end;
vIndex := pEmpList.Next(vIndex);
end loop;
end if;
end;
---------
procedure OutputRecs(pEmpList TEmpList) is
vIndex PLS_integer;
begin
if pEmpList is not null then
vIndex := pEmpList.First;
while vIndex is not null
loop
DBMS_OUTPUT.PUT_LINE ( 'pEmpList(' || vIndex ||') = '|| pEmpList(vIndex).EmployeeID||', '|| pEmpList(vIndex).LastName);
vIndex := pEmpList.Next(vIndex);
end loop;
end if;
end;
begin
vEmpList := TEmpList(EmpRec(100),
EmpRec( 34),
EmpRec(104),
EmpRec(110));
SearchRecs(vEmpList);
OutputRecs(vEmpList);
end;
/
Above program takes input value one at time.
However, i tried as below but unable to succeed.
i tried to give input from console at once like (100,34,104,100) in place of either hard coding the input (or) giving one input at time.
Snippet in DECLARE section:
declare
type TEmpRec is record (
EmployeeID EMPLOYEES.EMPLOYEE_ID%TYPE,
LastName EMPLOYEES.LAST_NAME%TYPE
);
type TEmpList is table of TEmpRec;
v_input TEmpList := TEmpList(&v_input); -- to read multiple input at once
vEmpList TEmpList;
In the final BEGIN section:
BEGIN
FOR j IN v_input.FIRST .. v_input.LAST LOOP
vEmpList := TEmpList(EmpRec(v_input(j).EmployeeID)); --to assign input values to vEmptList
SearchRecs(vEmpList);
OutputRecs(vEmpList);
end loop;
end;
/
Error in DECLARE section:
PLS-00306: wrong number or types of arguments in call to 'TEMPLIST'
Error in LAST BEGIN section:
PLS-00320: the declaration of the type of this expression is incomplete or malformed
As an example: at time, i am able to read multiple input values for same variable but i am unable to pass this as an input but unable to figure out how can make this as an input my main program.
DECLARE
TYPE t IS TABLE OF VARCHAR2(100);
ORDERS t := t(&ORDERS);
BEGIN
FOR j IN ORDERS.FIRST .. ORDERS.LAST LOOP
dbms_output.put_line(ORDERS(j));
END LOOP;
END;
/
Output:
PL/SQL procedure successfully completed.
Enter value for orders: 321,153,678
321
153
678
Thank You.
Since You have a collection of record variable, you need to pass employee_ids and employee last_names separately. How are you planning to pass them in a single shot?.
Here is a sample script which accomplishes something you want with 2 inputs for 3 collection elements.
First, create a collection TYPE and a PIPELINED function to convert comma separated values into Collections - f_convert2.
CREATE TYPE test_type AS TABLE OF VARCHAR2(100);
CREATE OR REPLACE FUNCTION f_convert2(p_list IN VARCHAR2)
RETURN test_type
PIPELINED
AS
l_string LONG := p_list || ',';
l_comma_index PLS_INTEGER;
l_index PLS_INTEGER := 1;
BEGIN
LOOP
l_comma_index := INSTR(l_string, ',', l_index);
EXIT WHEN l_comma_index = 0;
PIPE ROW ( SUBSTR(l_string, l_index, l_comma_index - l_index) );
l_index := l_comma_index + 1;
END LOOP;
RETURN;
END f_convert2;
/
Then in your anonymous blocks pass values for employee_ids and last_name separately.
SET SERVEROUTPUT ON
DECLARE
TYPE temprec IS RECORD ( employeeid employees.employee_id%TYPE,
lastname employees.last_name%TYPE );
TYPE templist IS
TABLE OF temprec;
vemplist templist;
v_no_of_rec NUMBER := 10;
v_empl_ids VARCHAR2(100) := '&empl_ids';
v_empl_lnames VARCHAR2(100) := '&empl_lnames';
BEGIN
SELECT employee_id,last_name
BULK COLLECT
INTO
vemplist
FROM
(
SELECT
ROWNUM rn,
column_value employee_id
FROM
TABLE ( f_convert2(v_empl_ids) )
) a
JOIN (
SELECT
ROWNUM rn,
column_value last_name
FROM
TABLE ( f_convert2(v_empl_lnames) )
) b ON a.rn = b.rn;
FOR i in 1..vemplist.COUNT LOOP
DBMS_OUTPUT.PUT_LINE(vemplist(i).employeeid || ' ' ||vemplist(i).lastname);
END LOOP;
END;
/
Instead of simple JOIN above if you use OUTER JOIN ( FULL or LEFT ), you can handle missing values without writing logic to check each value.

compare xtable count with table in oracle procedure

(I have several tables and for each table I have corresponding xtable.
what I am trying to do is create oracle procedure that will compare counts between xtable and table,
if (xtable >= table)
do something...
type namesarray IS VARRAY(5) OF VARCHAR2(10);
tbl_names namesarray;
xtbl_names namesarray;
total integer;
BEGIN
tbl_names := namesarray('tbl1', 'tbl2', 'tbl3', 'tbl4');
xtbl_names := namesarray('xtbl1', 'xtbl2', 'xtbl3', 'xtbl4');
total := tbl_names.count;
FOR i in (
SELECT COUNT(*) as TBL_COUNTS FROM tbl_names(i);
SELECT COUNT(*) as XTBL_COUNTS FROM xtbl_names(i)
)
LOOP
IF(i.XTBL_COUNTS >= i.TBL_COUNTS )
THEN
-- print to console
END IF;
END LOOP;
END;
This is all I got so far.
Can someone help?
How about this?
if (xtable >= table)
do something...
type namesarray IS VARRAY(5) OF VARCHAR2(10);
tbl_names namesarray;
xtbl_names namesarray;
total integer;
v_count_1 integer;
v_count_2 integer;
BEGIN
tbl_names := namesarray('tbl1', 'tbl2', 'tbl3', 'tbl4');
xtbl_names := namesarray('xtbl1', 'xtbl2', 'xtbl3', 'xtbl4');
total := tbl_names.count;
FOR i in (
select
t1.table_name as tab_1_name, t2.table_name as tab_2_name
from ( select table_name from user_tables where table_name in (...) ) t1
,( select 'x'||table_name as table_name from user_tables where table_name in (...) ) t2
where 'x'||t1.table_name = t2.table_name
)
LOOP
execute immediate 'select count(*) from '||i.tab_1_name into v_count_1;
execute immediate 'select count(*) from '||i.tab_2_name into v_count_2;
IF( v_count_1 >= v_count_2 )
THEN
-- print to console
END IF;
END LOOP;
END;

Resources