Accessing record fields by their name in Oracle - oracle

I want to know if there exists something like Reflection API in PL/SQL or not.
I have a table like
create table my_table (
id number,
value1 number,
value2 number,
value3 number,
value4 number,
value5 number);
And I have a variable as
rec as my_table%rowtype
... fill rec
insert into my_table values rec;
is there any way I can populate rec field dynamically by its name.
I mean I know the index (in this case something between 1 and 5), so I want to set 'value'||index to something.
As in my real case the last index is much more than 5, using a set of if/elsif is not proper. By the way number of fields is increased in long term (for example value6, value7 ma be added next year and so on, so I wan to write some kind of code not to be changed on every new column).

You can access variables in a program using dynamic SQL only if they are available globally. If you declare you record in the spec you can build utility functions that will use EXECUTE IMMEDIATE to build a small PL/SQL block to set the value. Here is a simple example of what you are looking for. Notice you can overload the set procedure to keep your datatypes intact.
CREATE TABLE my_table (
value1 NUMBER,
value2 VARCHAR2(100),
value3 DATE);
CREATE OR REPLACE PACKAGE pkg_my_table IS
my_table_rec my_table%ROWTYPE;
FUNCTION build_statement(i_record IN VARCHAR2,
i_field IN VARCHAR2) RETURN VARCHAR2;
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN VARCHAR2);
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN NUMBER);
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN DATE);
PROCEDURE insert_a_row;
END pkg_my_table;
/
CREATE OR REPLACE PACKAGE BODY pkg_my_table IS
FUNCTION build_statement(i_record IN VARCHAR2,
i_field IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN
RETURN 'BEGIN ' || lower($$PLSQL_UNIT) || '.' || i_record || '.' || i_field || ' := :x; END;';
END build_statement;
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN VARCHAR2) IS
BEGIN
EXECUTE IMMEDIATE build_statement(i_record => i_record,
i_field => i_field)
USING i_value;
END set_value;
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN NUMBER) IS
BEGIN
EXECUTE IMMEDIATE build_statement(i_record => i_record,
i_field => i_field)
USING i_value;
END set_value;
PROCEDURE set_value(i_record IN VARCHAR2,
i_field IN VARCHAR2,
i_value IN DATE) IS
BEGIN
EXECUTE IMMEDIATE build_statement(i_record => i_record,
i_field => i_field)
USING i_value;
END set_value;
PROCEDURE insert_a_row IS
BEGIN
my_table_rec := NULL;
set_value(i_record => 'my_table_rec',
i_field => 'value1',
i_value => 42);
set_value(i_record => 'my_table_rec',
i_field => 'value2',
i_value => 'forty-two');
set_value(i_record => 'my_table_rec',
i_field => 'value3',
i_value => to_date('1/1/1942',
'mm/dd/yyyy'));
INSERT INTO my_table
VALUES my_table_rec;
END insert_a_row;
END pkg_my_table;
/
BEGIN
pkg_my_table.insert_a_row;
END;
/
SELECT *
FROM my_table;
-- 42 forty-two 1/1/1942
Beware globals: you need to reset them correctly before using them again or you might get data carried over from previous invocations. Setting an entire record type variable to NULL will reset all the underlying fields (handy).
You will also be prone to ORA-04068: existing state of packages has been discarded errors should you compile PL/SQL with globals where there are active sessions referencing your code. Generally this is not a problem, but it is a behavioral difference.

Related

How to make recursive a pl/sql code block which one work with several selects sentences?

I have a pl/sql code block I'd like to improve. It's the next:
SELECT REPLACE(v_tx_cuerpo, '${param1}', rec.param1)
INTO v_tx_cuerpo
from DUAL;
SELECT REPLACE(v_tx_cuerpo, '${param2}', rec.param2)
INTO v_tx_cuerpo
from DUAL;
SELECT REPLACE(v_tx_cuerpo, '${param3}', rec.param3)
INTO v_tx_cuerpo
from DUAL;
SELECT REPLACE(v_tx_cuerpo, '${param4}', rec.param4)
INTO v_tx_cuerpo
from DUAL;
SELECT REPLACE(v_tx_cuerpo, '${param5}', rec.param5)
INTO v_tx_cuerpo
from DUAL;
As you see, it's repetitive. I'll explain it. v_tx_cuerpo is a text block which has some similar parameters. I want to replace them for some specific values. Each one of this parameters have names ${param#} and each one must be replace by one specific value .
What I'd like is, can run it in a loop, make it recursive. Can someone helpme whit this?
You haven't described your rec type, so I'll show an example how to make it more independent:
declare
type t_rec is record(
param1 varchar2(30),
param2 varchar2(30),
param3 varchar2(30),
param4 varchar2(30),
param5 varchar2(30)
);
rec t_rec;
v_tx_cuerpo varchar2(4000);
function f_subst(str varchar2, template varchar2, subst ora_name_list_t) return varchar2
as
res varchar2(32767):=str;
begin
for i in 1..subst.count loop
res:=replace(res, replace(template,'%d',i), subst(i));
end loop;
return res;
end;
begin
v_tx_cuerpo:='p1:${param1};p2:${param2};p3:${param3};p4:${param4};p5:${param5};';
v_tx_cuerpo:=f_subst(
v_tx_cuerpo,
'${param%d}',
ora_name_list_t('str1','str2','str3','str4','str5')
);
dbms_output.put_line(v_tx_cuerpo);
end;
/
as you can see I've created function f_subst that takes 3 arguments:
str varchar2 - input string for replacement
template varchar2 - string mask for replacement, in your example it's ${param%d}
subst ora_name_list_t - that is a collection defined as TYPE ora_name_list_t IS TABLE OF VARCHAR2(2*(ORA_MAX_NAME_LEN+2)+1), so you can specify any number of string for replacement. In this example, I've put str1 to str5.
So this function iterates input collection elements and replaces any substrings that match an input template mask with the value from this collection.
Results:
p1:str1;p2:str2;p3:str3;p4:str4;p5:str5;
And finally using your original rec:
declare
type t_rec is record(
param1 varchar2(30),
param2 varchar2(30),
param3 varchar2(30),
param4 varchar2(30),
param5 varchar2(30)
);
rec t_rec;
v_tx_cuerpo varchar2(4000);
function f_subst(str varchar2, template varchar2, subst ora_name_list_t) return varchar2
as
res varchar2(32767):=str;
begin
for i in 1..subst.count loop
res:=replace(res, replace(template,'%d',i), subst(i));
end loop;
return res;
end;
begin
v_tx_cuerpo:='p1:${param1};p2:${param2};p3:${param3};p4:${param4};p5:${param5};';
rec.param1:='str1';
rec.param2:='str2';
rec.param3:='str3';
rec.param4:='str4';
rec.param5:='str5';
v_tx_cuerpo:=f_subst(
v_tx_cuerpo,
'${param%d}',
ora_name_list_t(
rec.param1,
rec.param2,
rec.param3,
rec.param4,
rec.param5
)
);
dbms_output.put_line(v_tx_cuerpo);
end;
/

Needs fixing a procedure for inserting values to the table dynamically

I am trying to use dynamic SQL to insert values into my table. But I am struggling with it! This is my table
CREATE TABLE CARS
(
ID INTEGER PRIMARY KEY,
Manufacturer VARCHAR2(1000),
Model VARCHAR2(1000),
Year INTEGER NOT NULL,
Category VARCHAR2(1000) NOT NULL,
Mileage NUMBER,
FuelType VARCHAR2(1000),
EngineVolume NUMBER,
DriveWheels VARCHAR2(1000),
GearBox VARCHAR2(1000),
Doors VARCHAR2(1000),
Wheel VARCHAR2(1000),
Color VARCHAR2(1000),
InteriorColor VARCHAR2(1000),
VIN VARCHAR2(1000),
LeatherInterior VARCHAR2(1000) NOT NULL,
Price VARCHAR2(1000) NOT NULL,
Clearence VARCHAR2(1000) NOT NULL
)
And I have created a trigger that will increment the id column automatically.
CREATE SEQUENCE cars_seq START WITH 93100;
CREATE OR REPLACE TRIGGER cars_id_inc
BEFORE INSERT ON cars FOR EACH ROW
BEGIN
:NEW.ID := CARS_SEQ.nextval;
END;
Then I have created a procedure that will insert values into the cars table.
CREATE OR REPLACE PROCEDURE insert_all_cars (p_values VARCHAR2) IS
v_stmt VARCHAR2(10000);
BEGIN
v_stmt := 'INSERT INTO CARS ' || ' VALUES ' || p_values;
EXECUTE IMMEDIATE v_stmt;
END;
When I am trying to insert values to the cars table using a procedure like this:
DECLARE
p_values VARCHAR2 := '(''new_manufacturer'', ''new_model'', ' || '2000' || ' ,''new_category'', ' || '2000' ||' ,''new_fueltype'', ' || '3.0' ||
' ,''new_drivewheels'',''new_gearbox'',''new_doors'',''new_wheel'',''new_color'',
''new_interior_color'',''new_vin'',''new_leather_interior'',''new_price'',''new_clearence'')';
BEGIN
insert_all_cars(p_values);
END;
I am getting this kind of error:
Error starting at line : 60 in command -
DECLARE
p_values VARCHAR2 := '(''new_manufacturer'', ''new_model'', ' || '2000' || ' ,''new_category'', ' || '2000' ||' ,''new_fueltype'', ' || '3.0' ||
' ,''new_drivewheels'',''new_gearbox'',''new_doors'',''new_wheel'',''new_color'',
''new_interior_color'',''new_vin'',''new_leather_interior'',''new_price'',''new_clearence'')';
BEGIN
insert_all_cars(p_values);
END;
Error report -
ORA-06550: line 2, column 14:
PLS-00215: String length constraints must be in range (1 .. 32767)
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
Also tried to put numbers without quotes got the same kind error.
How I can fix it?
You didn't define the length of p_values in your anonymous pl/sql block. But why use dynamic sql? This is a really poor use case for it. Why not this?
create or replace procedure insert_all_cars (
p_manufacturer VARCHAR2,
p_model VARCHAR2,
p_year INTEGER,
p_category VARCHAR2,
p_mileage NUMBER,
p_fueltype VARCHAR2,
p_enginevolume NUMBER,
p_drivewheels VARCHAR2,
p_gearbox VARCHAR2,
p_doors VARCHAR2,
p_wheel VARCHAR2,
p_color VARCHAR2,
p_interiorcolor VARCHAR2,
p_vin VARCHAR2,
p_leatherinterior VARCHAR2,
p_price VARCHAR2,
p_clearence VARCHAR2) is
begin
insert into cars (
Manufacturer,
Model,
Year,
Category,
Mileage,
FuelType,
EngineVolume,
DriveWheels,
GearBox,
Doors,
Wheel,
Color,
InteriorColor,
VIN,
LeatherInterior,
Price,
Clearence )
values (
p_manufacturer,
p_model,
p_year,
p_category,
p_mileage,
p_fueltype,
p_enginevolume,
p_drivewheels,
p_gearbox,
p_doors,
p_wheel,
p_color,
p_interiorcolor,
p_vin,
p_leatherinterior,
p_price,
p_clearence );
end;
/
And then this:
begin
insert_all_cars (
p_manufacturer => 'new_manufacturer',
p_model => 'new_model',
p_year => 2000,
p_category => 'new_category',
p_mileage => 2000,
p_fueltype => 'new_fueltype',
p_enginevolume => 3.0,
p_drivewheels => 'new_drivewheels',
p_gearbox => 'new_gearbox',
p_doors => 'new_doors',
p_wheel => 'new_wheel',
p_color => 'new_color',
p_interiorcolor => 'new_interior_color',
p_vin => 'new_vin',
p_leatherinterior => 'new_leather_interior',
p_price => 'new_price',
p_clearence => 'new_clearence'
);
commit;
end;
/

Procedure as an argument in PL/SQL

I want to execute a procedure which takes another procedure as an argument which has some other arguments or parameters. E.g.
ProcA(Proc_B(Name Varchar2, ID Varchar2))
Is this possible? If,so, please suggest me the possible solution.
This is not possible.
A procedure does not directly return a value. This is different to a function that does return a value.
So you could do:
ProcedureA( FunctionB( name, id ) )
(Note: This is not passing a function as an argument but is passing the result of the function as an argument.)
Like this:
DECLARE
FUNCTION FunctionB(
name IN VARCHAR2,
id IN NUMBER
) RETURN VARCHAR2
IS
BEGIN
RETURN name || id;
END;
PROCEDURE ProcedureA(
value IN VARCHAR2
)
IS
BEGIN
DBMS_OUTPUT.PUT_LINE( value );
END;
BEGIN
ProcedureA(
FunctionB(
name => 'A',
id => 1
)
);
END;
/
An alternative would be to use an output parameter from ProcedureA and an intermediate variable:
DECLARE
temp VARCHAR2(50);
PROCEDURE ProcedureB(
name IN VARCHAR2,
id IN NUMBER,
result OUT VARCHAR2
)
IS
BEGIN
result := name || id;
END;
PROCEDURE ProcedureA(
value IN VARCHAR2
)
IS
BEGIN
DBMS_OUTPUT.PUT_LINE( value );
END;
BEGIN
ProcedureB(
name => :name,
id => :id,
result => temp
);
ProcedureA( temp );
END;
/
But you cannot nest one procedure inside the call invoking another.

Print report with error REP-8: A run time error in the PL/SQL development environment (DE)

I have problem with generate report to PDF via Report Server.
When generating a report show an error:
REP-8: A run time error in the PL/SQL development environment (DE)
PDE-PSD001 Could not resolve reference to while loading
REP-0008: An unexpected memory error occurred while initializing preferences.
The error occurs randomly. Once the report prints without problems and at a second printing report does not print and an error shown above,
although the report contains the same data.
My report use 3 Ref cursors and 3 select query. Use Report builder version 11.1.2.2.0.
Please, knows anyone where the problem is and how to resolve it?
Here is example, how I call Ref cusrcor:
Package:
TYPE t_info_rec IS RECORD (
column_1 VARCHAR2(15)
,column_2 NUMBER
,column_3 DATE
,column_4 VARCHAR2(15)
,column_5 VARCHAR2(15)
);
TYPE t_info_cur IS REF CURSOR RETURN t_info_rec;
PROCEDURE info(p_cur IN OUT t_info_cur
,p_parameter_1 NUMBER
,p_parameter_2 NUMBER
,p_parameter_3 VARCHAR2
,p_parameter_4 VARCHAR2
,p_parameter_5 NUMBER
,p_parameter_6 VARCHAR2);
Package Body:
PROCEDURE info(p_cur IN OUT t_info_cur
,p_parameter_1 NUMBER
,p_parameter_1 NUMBER
,p_parameter_1 VARCHAR2
,p_parameter_1 VARCHAR2
,p_parameter_1 NUMBER
,p_parameter_1 VARCHAR2)
IS
--
BEGIN
--
OPEN p_cur FOR
SELECT column_1,column_2,column_3,column_4,column_5
FROM table
;
--
EXCEPTION
WHEN OTHERS THEN
RAISE;
open p_cur for SELECT null,null,null,null,null FROM dual WHERE 1=0;
END info;
Call Ref cusrsor from Report Builders in RDF file:
function QR_2RefCurDS return package.t_info_cur is
c1 package.t_info_cur;
begin
package.info(c1
,p_parameter_1 => :P_PARAM1
,p_parameter_2 => :P_PARAM2
,p_parameter_3 => :P_PARAM3
,p_parameter_4 => :P_PARAM4
,p_parameter_5 => :P_PARAM5
,p_parameter_6 => :P_PARAM6
);
return c1;
end;
I am sorry for my English.
Thank you
Standa
There are few issue with your code. I can see that you declared type of ref cursor. Not sure if that can work. However without digging further in your code i show my code as below. In my code I am creating a record and passing the record type to procedure.
Solution 1
CREATE OR REPLACE PACKAGE Pkg_test
AS
TYPE t_info_rec IS RECORD
(
column_1 NUMBER,
column_2 NUMBER,
column_3 VARCHAR2 (15),
column_4 VARCHAR2 (15),
column_5 NUMBER
);
TYPE t_info_cur IS TABLE OF t_info_rec;
PROCEDURE info (p_cur IN OUT t_info_cur,
p_parameter_1 NUMBER,
p_parameter_2 NUMBER,
p_parameter_3 VARCHAR2,
p_parameter_4 VARCHAR2,
p_parameter_5 NUMBER,
p_parameter_6 VARCHAR2);
FUNCTION QR_2RefCurDS
RETURN t_info_cur;
END Pkg_test;
/
CREATE OR REPLACE PACKAGE BODY Pkg_test
as
PROCEDURE info(p_cur IN OUT t_info_cur
,p_parameter_1 NUMBER
,p_parameter_2 NUMBER
,p_parameter_3 VARCHAR2
,p_parameter_4 VARCHAR2
,p_parameter_5 NUMBER
,p_parameter_6 VARCHAR2)
IS
BEGIN
--OPEN p_cur FOR
SELECT p_parameter_1,p_parameter_2,p_parameter_3,p_parameter_4,p_parameter_5
bulk collect into p_cur
FROM dual ;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('In exception');
-- open p_cur for
SELECT null,null,null,null,null
bulk collect into p_cur
FROM dual WHERE 1=0;
End;
--Function Starting
FUNCTION QR_2RefCurDS
return t_info_cur
is
c1 t_info_cur;
begin
info(c1
,p_parameter_1 => 1 --:P_PARAM1
,p_parameter_2 => 2 --:P_PARAM2
,p_parameter_3 => 'Hello' --:P_PARAM3
,p_parameter_4 => 'Howru' --:P_PARAM4
,p_parameter_5 => 4 --:P_PARAM5
,p_parameter_6 => 'BYE' --:P_PARAM6
);
return c1;
end;
END Pkg_test;
/
Execution:
SQL> declare
outpt PKG_TEST.t_info_cur;
begin
outpt:=PKG_TEST.QR_2RefCurDS;
for i in 1..outpt.count
loop
dbms_output.put_line(outpt(i).column_1 ||' ' || outpt(i).column_2||' ' || outpt(i).column_3||' ' || outpt(i).column_4||' ' || outpt(i).column_5); end loop;
end;
/
1 2 Hello Howru 4
PL/SQL procedure successfully completed.
Solution 2
I use sys_refcursor to match your requirement:
CREATE OR REPLACE PACKAGE Pkg_test
AS
PROCEDURE info (p_cur IN OUT sys_refcursor,
p_parameter_1 NUMBER,
p_parameter_2 NUMBER,
p_parameter_3 VARCHAR2,
p_parameter_4 VARCHAR2,
p_parameter_5 NUMBER,
p_parameter_6 VARCHAR2);
FUNCTION QR_2RefCurDS
return sys_refcursor;
END Pkg_test;
/
CREATE OR REPLACE PACKAGE BODY Pkg_test
as
PROCEDURE info(p_cur IN OUT sys_refcursor
,p_parameter_1 NUMBER
,p_parameter_2 NUMBER
,p_parameter_3 VARCHAR2
,p_parameter_4 VARCHAR2
,p_parameter_5 NUMBER
,p_parameter_6 VARCHAR2)
IS
BEGIN
OPEN p_cur FOR
SELECT p_parameter_1,p_parameter_2,p_parameter_3,p_parameter_4,p_parameter_5
FROM dual ;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('In exception');
open p_cur for
SELECT null,null,null,null,null
--bulk collect into p_cur
FROM dual WHERE 1=0;
End;
--Function Starting
FUNCTION QR_2RefCurDS
return sys_refcursor
is
c1 sys_refcursor;
begin
info(c1
,p_parameter_1 => 1 --:P_PARAM1
,p_parameter_2 => 2 --:P_PARAM2
,p_parameter_3 => 'Hello' --:P_PARAM3
,p_parameter_4 => 'Howru' --:P_PARAM4
,p_parameter_5 => 4 --:P_PARAM5
,p_parameter_6 => 'BYE' --:P_PARAM6
);
return c1;
end;
END Pkg_test;
Execution
SQL> select PKG_TEST.QR_2RefCurDS from dual;
QR_2REFCURDS
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
:B5 :B4 :B3 :B2 :B1
---------- ---------- -------------------------------- -------------------------------- ----------
1 2 Hello Howru 4

PL/SQL print out ref cursor returned by a stored procedure

How can I fetch from a ref cursor that is returned from a stored procedure (OUT variable) and print the resulting rows to STDOUT in SQL*PLUS?
ORACLE stored procedure:
PROCEDURE GetGrantListByPI(p_firstname IN VARCHAR2, p_lastname IN VARCHAR2,
p_orderby IN VARCHAR2, p_cursor OUT grantcur);
PL/SQL:
SET SERVEROUTPUT ON;
DECLARE
TYPE r_cursor IS REF CURSOR;
refCursor r_cursor;
CURSOR grantCursor IS
SELECT last_name, first_name
FROM ten_year_pis
WHERE year_added = 2010;
last_name VARCHAR2(100);
first_name VARCHAR2(100);
BEGIN
OPEN grantCursor;
FETCH grantCursor INTO last_name, first_name;
WHILE grantCursor%FOUND LOOP
PMAWEB_PKG.GetGrantListByPI(last_name, first_name, 'last_name', refCursor);
--HOW DO I LOOP THROUGH THE RETURNED REF CURSOR (refCursor)
--AND PRINT THE RESULTING ROWS TO STDOUT?
FETCH grantCursor into last_name, first_name;
END LOOP;
CLOSE grantCursor;
END;
/
Note: This code is untested
Define a record for your refCursor return type, call it rec. For example:
TYPE MyRec IS RECORD (col1 VARCHAR2(10), col2 VARCHAR2(20), ...); --define the record
rec MyRec; -- instantiate the record
Once you have the refcursor returned from your procedure, you can add the following code where your comments are now:
LOOP
FETCH refCursor INTO rec;
EXIT WHEN refCursor%NOTFOUND;
dbms_output.put_line(rec.col1||','||rec.col2||','||...);
END LOOP;
You can use a bind variable at the SQLPlus level to do this. Of course you have little control over the formatting of the output.
VAR x REFCURSOR;
EXEC GetGrantListByPI(args, :x);
PRINT x;
If you want to print all the columns in your select clause you can go with the autoprint command.
CREATE OR REPLACE PROCEDURE sps_detail_dtest(v_refcur OUT sys_refcursor)
AS
BEGIN
OPEN v_refcur FOR 'select * from dummy_table';
END;
SET autoprint on;
--calling the procedure
VAR vcur refcursor;
DECLARE
BEGIN
sps_detail_dtest(vrefcur=>:vcur);
END;
Hope this gives you an alternate solution
More easier option is to use DBMS_SQL.return_result();
Lets say your package / procedure / cursor spec is as below.
create or replace PACKAGE my_package IS
TYPE my_ref_cursor_type IS REF CURSOR;
PROCEDURE my_procedure (
p_in_param1 IN VARCHAR2,
p_in_param2 IN VARCHAR2,
p_in_param3 IN VARCHAR2,
p_my_ref_cursor OUT my_ref_cursor_type,
p_err_code OUT NUMBER,
p_err_msg OUT VARCHAR2
);
END my_package;
Try this to invoke the procedure from your sql developer WORKSHEET
SET SERVEROUTPUT ON;
DECLARE
P_MY_REF_CURSOR my_schema.my_package.my_ref_cursor_type;
P_ERR_CODE NUMBER;
P_ERR_MSG VARCHAR2(200);
BEGIN
my_package.my_procedure(
'VALUE1',
'VALUE2',
'VALUE3',
P_MY_REF_CURSOR => P_MY_REF_CURSOR,
P_ERR_CODE => P_ERR_CODE,
P_ERR_MSG => P_ERR_MSG
);
DBMS_OUTPUT.PUT_LINE(P_ERR_MSG);
DBMS_OUTPUT.PUT_LINE(P_ERR_CODE);
DBMS_SQL.return_result(P_MY_REF_CURSOR);
END;
Hope this helps !
There are many ways for displaying the sys_refcursor result set and one of them that is so easy is using SQL Developer to fetch sys_refcursor and print output which are:
Create a test function to print its result
Execute the function
View the output
Verify the result set

Resources