Cursor as in parameter - refactoring a procedure - oracle

I have many functions similiar to this one:
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
PROCEDURE REP_HELPER1 (myIdx IN BINARY_INTEGER, from_d IN DATE, rep_table IN OUT rep_table_T) IS
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CURSOR myCUR1 IS SELECT myField1,
myField2,
myField3,
myField4,
myField5,
myField6,
myField7,
myField8,
myField9,
myField10,
myField11,
myField12,
myField13,
myField14,
myField15,
myField16,
myField17,
myField18,
myField19,
myField20,
myField21,
myField22,
myField23,
myField24,
myField25,
myField26,
myField27,
myField28,
myField29,
myField30,
myField31
FROM myTable;
BEGIN
-- I wish to move the part below to different procedure
OPEN myCUR1;
FETCH myCUR1 INTO rep_table(myIdx).day1, rep_table(myIdx).day2, rep_table(myIdx).day3, rep_table(myIdx).day4, rep_table(myIdx).day5,
rep_table(myIdx).day6, rep_table(myIdx).day7, rep_table(myIdx).day8, rep_table(myIdx).day9, rep_table(myIdx).day10,
rep_table(myIdx).day11, rep_table(myIdx).day12, rep_table(myIdx).day13, rep_table(myIdx).day14, rep_table(myIdx).day15,
rep_table(myIdx).day16, rep_table(myIdx).day17, rep_table(myIdx).day18, rep_table(myIdx).day19, rep_table(myIdx).day20,
rep_table(myIdx).day21, rep_table(myIdx).day22, rep_table(myIdx).day23, rep_table(myIdx).day24, rep_table(myIdx).day25,
rep_table(myIdx).day26, rep_table(myIdx).day27, rep_table(myIdx).day28, rep_table(myIdx).day29, rep_table(myIdx).day30,
rep_table(myIdx).day31;
CLOSE myCUR1;
END REP_HELPER1;
I wish to do the part from open myCUR; to close myCUR; in a separate univesral procedure. As I have many functions like the above and the cursor is always different. So I would like to have one procedure which would do the open,fetch, close part :
PROCEDURE PB_HELPER_READ_INTO_DAYS(nIndex IN BINARY_INTEGER, myCUR by reference, rep_table IN OUT rep_table_T)
Is it possible to to it in plsql?
EDIT:
Based on yours clues I wrote it like this:
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
PROCEDURE REP_HELPER1 (myIdx IN BINARY_INTEGER, from_d IN DATE, rep_table IN OUT rep_table_T) IS
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
myCUR1 SYS_REFCURSOR;
BEGIN
OPEN myCUR1 FOR SELECT myField1,
myField2,
myField3,
myField4,
myField5,
myField6,
myField7,
myField8,
myField9,
myField10,
myField11,
myField12,
myField13,
myField14,
myField15,
myField16,
myField17,
myField18,
myField19,
myField20,
myField21,
myField22,
myField23,
myField24,
myField25,
myField26,
myField27,
myField28,
myField29,
myField30,
myField31
FROM myTable;
MY_READ(myIdx , myCUR1, rep_table)
END REP_HELPER1;
PROCEDURE MY_READ(myIdx IN BINARY_INTEGER, cur IN SYS_REFCURSOR, rep_table IN OUT rep_table_T) IS
BEGIN
FETCH cur INTO rep_table(myIdx).day1, rep_table(myIdx).day2, rep_table(myIdx).day3, rep_table(myIdx).day4, rep_table(myIdx).day5,
rep_table(myIdx).day6, rep_table(myIdx).day7, rep_table(myIdx).day8, rep_table(myIdx).day9, rep_table(myIdx).day10,
rep_table(myIdx).day11, rep_table(myIdx).day12, rep_table(myIdx).day13, rep_table(myIdx).day14, rep_table(myIdx).day15,
rep_table(myIdx).day16, rep_table(myIdx).day17, rep_table(myIdx).day18, rep_table(myIdx).day19, rep_table(myIdx).day20,
rep_table(myIdx).day21, rep_table(myIdx).day22, rep_table(myIdx).day23, rep_table(myIdx).day24, rep_table(myIdx).day25,
rep_table(myIdx).day26, rep_table(myIdx).day27, rep_table(myIdx).day28, rep_table(myIdx).day29, rep_table(myIdx).day30,
rep_table(myIdx).day31;
CLOSE cur;
END MY_READ;

Option which would work is to create a package, declare cursor globally and use it in any procedure you want. For example:
SQL> create or replace package pkg_test is
2 procedure p1;
3 end;
4 /
Package created.
SQL> create or replace package body pkg_test is
2 cursor c1 is select * from dept;
3 c1r c1%rowtype;
4
5
6 procedure p1 is
7 begin
8 open c1;
9 fetch c1 into c1r;
10 close c1;
11 end p1;
12 end;
13 /
Package body created.
SQL>
This won't work: declaring a cursor in one procedure and working with it in another:
SQL> create or replace package pkg_test is
2 procedure p1;
3 procedure p2;
4 end;
5 /
Package created.
SQL> create or replace package body pkg_test is
2 procedure p1 is
3 cursor c1 is select * from dept;
4 c1r c1%rowtype;
5 begin
6 null;
7 end p1;
8
9
10 procedure p2 is
11 begin
12 open c1;
13 fetch c1 into p1.c1r;
14 close c1;
15 end p2;
16 end;
17 /
Warning: Package Body created with compilation errors.
SQL> show err
Errors for PACKAGE BODY PKG_TEST:
LINE/COL ERROR
-------- -----------------------------------------------------------------
12/5 PL/SQL: SQL Statement ignored
12/11 PLS-00201: identifier 'C1' must be declared
13/5 PL/SQL: SQL Statement ignored
13/11 PLS-00201: identifier 'C1' must be declared
14/5 PL/SQL: SQL Statement ignored
14/11 PLS-00201: identifier 'C1' must be declared
SQL>
Also, you can't reference it using the "owner" procedure's prefix:
SQL> create or replace package body pkg_test is
2 procedure p1 is
3 cursor c1 is select * from dept;
4 c1r c1%rowtype;
5 begin
6 null;
7 end p1;
8
9
10 procedure p2 is
11 begin
12 open p1.c1;
13 fetch p1.c1 into p1.c1r;
14 close p1.c1;
15 end p2;
16 end;
17 /
Warning: Package Body created with compilation errors.
SQL> show err
Errors for PACKAGE BODY PKG_TEST:
LINE/COL ERROR
-------- -----------------------------------------------------------------
12/5 PL/SQL: SQL Statement ignored
12/14 PLS-00225: subprogram or cursor 'P1' reference is out of scope
13/5 PL/SQL: SQL Statement ignored
13/11 PLS-00225: subprogram or cursor 'P1' reference is out of scope
14/5 PL/SQL: SQL Statement ignored
14/11 PLS-00225: subprogram or cursor 'P1' reference is out of scope
SQL>

You can define the cursor in a package spec outside of any procedure or function. Then use that cursor virtually any where a cursor is valid (except as a reference cursor). Includes any procedure/function within the package or any standalone procedure/function, and even an anonymous block. Just be sure to reference as package_name.cursor_name anywhere outside of the package. See demo)
create or replace package pkg_test is
cursor c_dept is select * from dept;
procedure p1;
procedure p2;
end pkg_test;
/
This makes maintenance of the cursor quite easy since there is only one definition, so only one place of maintenance.

You can put only FETCH and CLOSE in a different procedure. Would be this (when you have only one OUT parameter, then I prefer a FUNCTION):
CREATE OR REPLACE FUNCTION REP_HELPER (myIdx IN BINARY_INTEGER, from_d IN DATE) RETURN SYS_REFCURSOR IS
myCur SYS_REFCURSOR;
BEGIN
OPEN myCur FOR
SELECT myField1, ...
FROM myTable;
RETURN myCur;
END REP_HELPER;
And use it like this:
DECLARE
cur SYS_REFCURSOR;
BEGIN
cur := REP_HELPER(...);
FETCH cur INTO ...
CLOSE cur;
END;
A more advanced solution would be dynamic SQL with DBMS_SQL Package:
CREATE OR REPLACE FUNCTION REP_HELPER(myIdx IN BINARY_INTEGER, from_d IN DATE) RETURN NUMBER IS
curid NUMBER := DBMS_SQL.OPEN_CURSOR;
sql_stmt VARCHAR2(32000);
BEGIN
sql_stmt := 'SELECT myField1, ... FROM myTable';
DBMS_SQL.PARSE(curid, sql_stmt, DBMS_SQL.NATIVE);
RETURN curid;
END REP_HELPER;
DECLARE
cur SYS_REFCURSOR;
curid NUMBER;
ret INTEGER;
BEGIN
curid := REP_HELPER(...);
ret := DBMS_SQL.EXECUTE(curid);
-- Switch from DBMS_SQL to native dynamic SQL
cur := DBMS_SQL.TO_REFCURSOR(curid);
FETCH cur INTO ...
CLOSE cur;
END;
or
CREATE OR REPLACE PROCEDURE REP_HELPER(curid IN OUT NUMBER, myIdx IN BINARY_INTEGER, from_d IN DATE) IS
sql_stmt VARCHAR2(32000);
BEGIN
sql_stmt := 'SELECT myField1, ... FROM myTable';
DBMS_SQL.PARSE(curid, sql_stmt, DBMS_SQL.NATIVE);
END REP_HELPER;
DECLARE
cur SYS_REFCURSOR;
curid NUMBER;
ret INTEGER;
BEGIN
curid NUMBER := DBMS_SQL.OPEN_CURSOR;
REP_HELPER(curid, ...);
ret := DBMS_SQL.EXECUTE(curid);
-- Switch from DBMS_SQL to native dynamic SQL
cur := DBMS_SQL.TO_REFCURSOR(curid);
FETCH cur INTO ...
CLOSE cur;
END;
But I think this would be an overkill.
Update:
You can compose the SQL string also dynamically, e.g.:
sql_stmt := 'SELECT ';
FOR i IN 1..31 LOOP
sql_stmt := sql_stmt || 'myField'||i||',';
END LOOP;
sql_stmt := REGEXP_REPLACE(sql_stmt, ',$');
sql_stmt := sql_stmt || ' FROM '||table_name;
sql_stmt := sql_stmt || ' WHERE the_date = :d';
OPEN cur FOR sql_stmt USING from_d;

Related

Error when calling procedure from procedure

The following procedure compilation error occurred:
Procedure A receives the result from table B, inputs it to the GLOBAL TEMPORARY TABLE, and retrieves the final result after the operation.
Procedure B is a function that manipulates and retrieves the source data.
When I run the A procedure, I get the following compilation error:
/* GLOBAL TEMPORARY TABLE */
CREATE GLOBAL TEMPORARY TABLE TT_TB_TMP
(
TABLE_NAME VARCHAR2(200)
,COLUMN_NAME VARCHAR2(200)
)
ON COMMIT DELETE ROWS
NOPARALLEL;
/* PROCEDURE B(SP_TEST_H2)*/
create or replace PROCEDURE SP_TEST_H2
(
p_TBL_NAME IN VARCHAR
)
AS
v_cursor SYS_REFCURSOR;
BEGIN
OPEN v_cursor FOR
SELECT TABLE_NAME, COLUMN_NAME
FROM ALL_TAB_COLUMNS
WHERE TABLE_NAME = 'ALL_XML_SCHEMAS'; -- p_TBL_NAME
DBMS_SQL.RETURN_RESULT(v_cursor);
END SP_TEST_H2;
/* PROCEDURE A(SP_TEST_H1)*/
create or replace PROCEDURE SP_TEST_H1
(
p_TBL_NAME IN VARCHAR
)
AS
v_cursor SYS_REFCURSOR;
BEGIN
DECLARE
cv_ins SYS_REFCURSOR;
v_temp TT_TB_TMP%ROWTYPE;
BEGIN
cv_ins := SP_TEST_H2('XXX');
LOOP
FETCH cv_ins INTO v_temp;
EXIT WHEN cv_ins%NOTFOUND;
INSERT INTO TT_TB_TMP VALUES v_temp;
END LOOP;
CLOSE cv_ins;
/*
OPEN v_cursor FOR
SELECT * FROM TT_TB_TMP;
DBMS_SQL.RETURN_RESULT(v_cursor);
*/
END;
END SP_TEST_H1
PLS-00222: Function with name 'SP_TEST_H2' does not exist in scope
What did I do wrong?
If you're returning something, then use a function - they are designed for such a purpose.
That's what Oracle told you:
Function with name 'SP_TEST_H2' does not exist in scope
which is related to this line in your code:
cv_ins := SP_TEST_H2('XXX');
Function:
SQL> CREATE OR REPLACE FUNCTION sf_test_h2 (p_tbl_name IN VARCHAR)
2 RETURN SYS_REFCURSOR
3 AS
4 v_cursor SYS_REFCURSOR;
5 BEGIN
6 OPEN v_cursor FOR SELECT table_name, column_name
7 FROM all_tab_columns
8 WHERE table_name = p_tbl_name;
9
10 RETURN v_cursor;
11 END sf_test_h2;
12 /
Function created.
Procedure:
SQL> CREATE OR REPLACE PROCEDURE sp_test_h1 (p_tbl_name IN VARCHAR)
2 AS
3 cv_ins SYS_REFCURSOR;
4 v_temp tt_tb_tmp%ROWTYPE;
5 BEGIN
6 cv_ins := sf_test_h2 (p_tbl_name);
7
8 LOOP
9 FETCH cv_ins INTO v_temp;
10
11 EXIT WHEN cv_ins%NOTFOUND;
12
13 INSERT INTO tt_tb_tmp (table_name, column_name)
14 VALUES (v_temp.table_name, v_temp.column_name);
15 END LOOP;
16
17 CLOSE cv_ins;
18 END sp_test_h1;
19 /
Procedure created.
Testing:
SQL> EXEC sp_test_h1('DEPT');
PL/SQL procedure successfully completed.
SQL> SELECT * FROM tt_tb_tmp;
TABLE_NAME COLUMN_NAME
-------------------- --------------------
DEPT LOC
DEPT DNAME
DEPT DEPTNO
SQL>

Oracle PLSQL refcursor%ROWTYPE the declaration of the type of this expression is incomplete or malformed

I want to assign cursor variable in PL/SQL code and then fetch a row from that. When I declared cursor explicitly, it works.
DECLARE
cursor c1 is SELECT * from acme;
row1 c1%ROWTYPE;
but when I use code to assign cursor per e.g. Oracle PLSQL setting a cursor from a variable like that:
c1 sys_refcursor;
r1 c1%ROWTYPE;
I get error
the declaration of the type of this expression is incomplete or
malformed
writting just r1 %ROWTYPE produces error too.
As per general use of variables explained e.g. here How to declare variable and use it in the same Oracle SQL script? I've tried:
SET SERVEROUTPUT ON
DECLARE
c1 sys_refcursor;
BEGIN
open c1 for 'SELECT * FROM foo';
declare r1 c1%ROWTYPE;
begin
fetch c1 into r1;
end;
DBMS_OUTPUT.PUT_LINE('FOO');
close c1;
END;
and again got the declaration of the type of this expression is incomplete or malformed
How can I declare variable of row type it my case? Oracle 11g.
P.S. workaround would be to fetch c1 into v_empno, v_ename, ect declaring several variables, still, is there a way to use rowtype one?
Something like this?
SQL> set serveroutput on
SQL>
SQL> declare
2 c1 sys_refcursor;
3 r1 dept%rowtype; --> this
4 begin
5 open c1 for 'select * from dept';
6 fetch c1 into r1;
7 dbms_output.put_line(r1.dname);
8 end;
9 /
ACCOUNTING
PL/SQL procedure successfully completed.
SQL>
If your intention is to display result from any given query, you may use this standard procedure similar to the one described in this AskTOM article
create or replace procedure return_result( l_query varchar2 )
is
l_theCursor integer default dbms_sql.open_cursor;
l_columnValue varchar2(4000);
l_status integer;
l_colCnt number := 0;
l_separator varchar2(1);
l_descTbl dbms_sql.desc_tab;
begin
dbms_sql.parse( l_theCursor, l_query, dbms_sql.native );
dbms_sql.describe_columns( l_theCursor, l_colCnt, l_descTbl );
l_separator := '';
for i in 1 .. l_colCnt loop
dbms_output.put( l_separator || l_descTbl(i).col_name );
l_separator := ',';
end loop;
dbms_output.put_line('');
for i in 1 .. l_colCnt loop
dbms_sql.define_column( l_theCursor, i, l_columnValue, 4000 );
end loop;
l_status := dbms_sql.execute(l_theCursor);
while ( dbms_sql.fetch_rows(l_theCursor) > 0 ) loop
l_separator := '';
for i in 1 .. l_colCnt loop
dbms_sql.column_value( l_theCursor, i, l_columnValue );
dbms_output.put( l_separator || l_columnValue );
l_separator := ',';
end loop;
dbms_output.new_line;
end loop;
dbms_sql.close_cursor(l_theCursor);
end;
/
Pass any query to this procedure to get result.
set serveroutput on
set feedback off
set sqlformat ansiconsole
set pages 0
BEGIN
return_result('select e.employee_id,e.first_name,e.salary,
d.department_name from
employees e join
departments d on e.department_id= d.department_id');
END;
/
Result
EMPLOYEE_ID,FIRST_NAME,SALARY,DEPARTMENT_NAME
200,Jennifer,4400,Administration
201,Michael,13000,Marketing
202,Pat,6000,Marketing
114,Den,11000,Purchasing
Note: Oracle 12c provides DBMS_SQL.RETURN_RESULT using which this procedure can be easily implemented.

Dynamic select execution missing expression error

I am using Oracle 12, and I want to make a dynamic procedure which selects rows from specific table but according to an unknown conditio. That condition will be specified as input parameter.
Suppose I have a column called employee id and I want to call the procedure
with the following condition
execute s('employeeid = 2')
My code is
create or replace procedure s (condition varchar)
as
TYPE EmpCurTyp IS REF CURSOR; -- define weak REF CURSOR type
emp_cv EmpCurTyp; -- declare cursor variable
my_ename VARCHAR2(15);
my_sal NUMBER := 2;
mycondition varchar2(100):=condition;
BEGIN
OPEN emp_cv FOR -- open cursor variable
'SELECT employeeid, employeename FROM employees WHERE = :s' USING mycondition;
END;
but I am getting an error
missing expression
What am I doing wrong, and will the result of this procedure be selected rows from employees table that satisfy applied condition ?
The USING is meant to handle values, not pieces of code; if you need to edit your query depending on an input parameter ( and I believe this is a very dangerous way of coding), you should treat the condition as a string to concatenate to the query.
For example, say you have this table:
create table someTable(column1 number)
This procedure does somthing similar to what you need:
create or replace procedure testDyn( condition IN varchar2) is
cur sys_refcursor;
begin
open cur for 'select column1 from sometable where ' || condition;
/* your code */
end;
Hot it works:
SQL> exec testDyn('column1 is null');
PL/SQL procedure successfully completed.
SQL> exec testDyn('column99 is null');
BEGIN testDyn('column99 is null'); END;
*
ERROR at line 1:
ORA-00904: "COLUMN99": invalid identifier
ORA-06512: at "ALEK.TESTDYN", line 4
ORA-06512: at line 1
This is not embedded in a procedure yet but I tested this and works:
DECLARE
TYPE OUT_TYPE IS TABLE OF VARCHAR2 (20)
INDEX BY BINARY_INTEGER;
l_cursor INTEGER;
l_fetched_rows INTEGER;
l_sql_string VARCHAR2 (250);
l_where_clause VARCHAR2 (100);
l_employeeid VARCHAR2 (20);
l_employeename VARCHAR2 (20);
l_result INTEGER;
o_employeeid OUT_TYPE;
o_employeename OUT_TYPE;
BEGIN
l_cursor := DBMS_SQL.OPEN_CURSOR;
l_sql_string := 'SELECT employeeid, employeename FROM employees WHERE ';
l_where_clause := 'employeeid = 2';
l_sql_string := l_sql_string || l_where_clause;
DBMS_SQL.PARSE (l_cursor, l_sql_string, DBMS_SQL.V7);
DBMS_SQL.DEFINE_COLUMN (l_cursor,
1,
l_employeeid,
20);
DBMS_SQL.DEFINE_COLUMN (l_cursor,
2,
l_employeename,
20);
l_fetched_rows := 0;
l_result := DBMS_SQL.EXECUTE_AND_FETCH (l_cursor);
LOOP
EXIT WHEN l_result = 0;
DBMS_SQL.COLUMN_VALUE (l_cursor, 1, l_employeeid);
DBMS_SQL.COLUMN_VALUE (l_cursor, 2, l_employeename);
l_fetched_rows := l_fetched_rows + 1;
o_employeeid (l_fetched_rows) := l_employeeid;
o_employeename (l_fetched_rows) := l_employeename;
l_result := DBMS_SQL.FETCH_ROWS (l_cursor);
END LOOP;
DBMS_SQL.CLOSE_CURSOR (l_cursor);
DBMS_OUTPUT.PUT_LINE (o_employeeid (1));
DBMS_OUTPUT.PUT_LINE (o_employeename (1));
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.PUT_LINE ('GENERAL FAILURE: ' || SQLERRM);
END;

calling stored procedure from anonymous block

I have problem reading result from plsql stored procedure written in sql developer on Oracle 11gr2 database on local machine.
This is my table:
create table MY_TEST_TABLE
(employee_id NUMBER(6)
,first_name VARCHAR2(20)
,last_name VARCHAR2(25)
,email VARCHAR2(25)
,phone_number VARCHAR2(20));
This is procedure declaration:
create or replace PACKAGE TEST_PACKAGE AS
procedure test_procedure (i_id in number,
o_data out sys_refcursor);
END TEST_PACKAGE;
This is body:
create or replace PACKAGE BODY TEST_PACKAGE AS
procedure test_procedure (i_id in number,
o_data out sys_refcursor) AS
BEGIN
open o_data for
select employee_id,
first_name,
last_name,
email,
phone_number
from my_test_table
where EMPLOYEE_ID = i_id;
close o_data;
END test_procedure;
END TEST_PACKAGE;
And this is anonymous block call:
SET serveroutput on
DECLARE
in_id number;
my_cursor sys_refcursor;
current_record my_test_table%ROWTYPE;
BEGIN
in_id := 1;
test_package.test_procedure(in_id, my_cursor);
open my_cursor;
LOOP
FETCH my_cursor INTO current_record;
EXIT WHEN my_cursor%NOTFOUND;
dbms_output.put_line(' - out - ' || current_record.employee_id);
END LOOP;
END;
I am getting error:
Error starting at line : 2 in command -
DECLARE
in_id number;
my_cursor sys_refcursor;
current_record my_test_table%ROWTYPE;
BEGIN
in_id := 1;
test_package.test_procedure(in_id, my_cursor);
open my_cursor;
LOOP
FETCH my_cursor INTO current_record;
EXIT WHEN my_cursor%NOTFOUND;
dbms_output.put_line(' - out - ' || current_record.employee_id);
END LOOP;
END;
Error report -
ORA-06550: line 8, column 5:
PLS-00382: expression is of wrong type
ORA-06550: line 8, column 5:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
Can someone explain what is wrong?
Tnx!
The cursor is opened in the procedure, so you don't need to, and can't, open it directly in your anonymous block. Well, it should be open, but you're also closing it in the procedure. Remove the close from the procedure, and the open from the block:
create or replace PACKAGE BODY TEST_PACKAGE AS
procedure test_procedure (i_id in number,
o_data out sys_refcursor) AS
BEGIN
open o_data for
select employee_id,
first_name,
last_name,
email,
phone_number
from my_test_table
where EMPLOYEE_ID = i_id;
-- close o_data;
END test_procedure;
END TEST_PACKAGE;
/
And:
DECLARE
in_id number;
my_cursor sys_refcursor;
current_record my_test_table%ROWTYPE;
BEGIN
in_id := 1;
test_package.test_procedure(in_id, my_cursor);
-- open my_cursor;
LOOP
FETCH my_cursor INTO current_record;
EXIT WHEN my_cursor%NOTFOUND;
dbms_output.put_line(' - out - ' || current_record.employee_id);
END LOOP;
END;
/
SQL Fiddle - just add data...

PL/SQL - execute immediate in pipelined function

I want to execute dynamic query in my pipelined function and return results of this query.
Is it possible to do this?
Pipelined function is convenient for me to achieve good interface for my application cause it behaves like a table.
The function:
CREATE OR REPLACE FUNCTION MyFunction(p_schema VARCHAR2) RETURN MyTableType Pipelined IS
v_query VARCHAR2(1000);
BEGIN
v_query := 'SELECT * FROM TABLE ('||p_schema||'.somepackage.SomeFunction)'; --SomeFunction is another pipelined function
EXECUTE IMMEDIATE v_query;
--Results of the v_query are compatible with MyTableType's row type. But how to return them from pipelined function?
END;
It is possible to combine dynamic SQL and pipelined function but the return type will not be dynamic: the number and type of columns returned will be fixed.
You can use EXECUTE IMMEDIATE with BULK COLLECT (thanks #be here now), dynamic cursors or DBMS_SQL to return more than one row. Here's an example with a dynamic cursor:
SQL> CREATE OR REPLACE PACKAGE pkg AS
2 TYPE test_tab IS TABLE OF test%ROWTYPE;
3 FUNCTION dynamic_cursor(l_where VARCHAR2) RETURN test_tab PIPELINED;
4 END;
5 /
Package created.
SQL> CREATE OR REPLACE PACKAGE BODY pkg IS
2 FUNCTION dynamic_cursor(l_where VARCHAR2) RETURN test_tab PIPELINED IS
3 cc sys_refcursor;
4 l_row test%ROWTYPE;
5 BEGIN
6 OPEN cc FOR 'SELECT * FROM test WHERE ' || l_where;
7 LOOP
8 FETCH cc INTO l_row;
9 EXIT WHEN cc%NOTFOUND;
10 PIPE ROW (l_row);
11 END LOOP;
12 RETURN;
13 END;
14 END;
15 /
Package body created.
Let's call this dynamic function:
SQL> SELECT *
2 FROM TABLE(pkg.dynamic_cursor('id <= 2'));
ID DAT
---------- ---
1 xxx
2 xxx
As always with dynamic SQL, beware of SQL Injection.
I think something like this:
CREATE OR REPLACE FUNCTION MyFunction(par1 VARCHAR2, ...) RETURN MyTableType Pipelined IS
v_query VARCHAR2(1000);
l_result MyTableType;
BEGIN
v_query := --My query created based on parameters
EXECUTE IMMEDIATE v_query into l_result;
pipe row(l_result);
END;
Works only if v_query returns 1 row.
I couldn't get #VincentMalgrat's answer to work. But it was very close. Definitely a big help in the right direction for me.
Here's what I got to work:
Package
CREATE OR REPLACE PACKAGE pkg AS
TYPE test_row IS RECORD ( test_name VARCHAR2 (255), test_number number, test_date date );
TYPE test_tab IS TABLE OF test_row;
FUNCTION dynamic_cursor(l_where VARCHAR2) RETURN test_tab PIPELINED;
END;
Package Body
CREATE OR REPLACE PACKAGE BODY pkg IS
FUNCTION dynamic_cursor(l_where VARCHAR2) RETURN test_tab PIPELINED IS
cc sys_refcursor;
l_row test_row;
BEGIN
OPEN cc FOR 'select name_column, number_column, date_column FROM my_table where number_column ='||l_where;
LOOP
FETCH cc INTO l_row;
EXIT WHEN cc%NOTFOUND;
PIPE ROW (l_row);
END LOOP;
RETURN;
END;
END;

Resources