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
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;
/
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;
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!
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.
(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;