Wrap select with an Oracle function - oracle

In my mind, I'm writing a function such that calling something like
select get_foo() from dual;
or
select * from table (get_foo);
returns the same result as
select * from foo;
So, I've got a function that compiles...
create or replace function get_foo return sys_refcursor as
rc_foo sys_refcursor;
begin
open rc_foo for 'select * from foo';
return rc_foo;
end;
but select get_foo() from dual returns 1 row.
((ID=1,NAME=Sarah1),(ID=2,NAME=Sarah2),(ID=3,NAME=Sarah3),)
whilst select * from table( get_foo() ) gives me ORA-22905.
How do I change the function definition and/or the call to get the desired outcome?

you use a pipelined function.
for example:
SQL> create table foo(id , name) as select rownum, 'Sarah'||rownum from dual connect by level <= 3;
Table created.
SQL> create or replace package pipeline_test
2 as
3 type foo_tab is table of foo%rowtype;
4 function get_foo
5 return foo_tab PIPELINED;
6 end;
7 /
Package created.
SQL> create or replace package body pipeline_test
2 as
3 function get_foo
4 return foo_tab PIPELINED
5 is
6 v_rc sys_refcursor;
7 t_foo foo_tab;
8
9 begin
10 open v_rc for select * from foo;
11 loop
12 fetch v_rc bulk collect into t_foo limit 100;
13 exit when t_foo.count = 0;
14 for idx in 1..t_foo.count
15 loop
16 pipe row(t_foo(idx));
17 end loop;
18 end loop;
19 end;
20 end;
21 /
Package body created.
SQL> select * from table(pipeline_test.get_foo());
ID NAME
---------- ---------------------------------------------
1 Sarah1
2 Sarah2
3 Sarah3

Related

Need to fetch the table details using stored procedure when we give table name as input

CREATE TABLE test_table
(
col1 NUMBER(10),
col2 NUMBER(10)
);
INSERT INTO test_table
VALUES (1, 2);
I am writing a stored procedure wherein if I give a table name as an input, that should give me the table data and column details.
For example:
SELECT *
FROM <input_table_name>;
But this causes an error that the SQL command has not ended properly even though I have taken care of this.
My attempt:
CREATE OR REPLACE PROCEDURE sp_test(iv_table_name IN VARCHAR2,
p_out_cur OUT SYS_REFCURSOR)
AS
lv_str VARCHAR2(400);
lv_count NUMBER(1);
lv_table_name VARCHAR2(255):=UPPER(iv_table_name);
BEGIN
lv_str := 'SELECT * FROM '||lv_table_name;
SELECT COUNT(1) INTO lv_count FROM all_tables WHERE table_name = lv_table_name;
IF lv_count = 0 THEN
dbms_output.put_line('Table does not exist');
ELSE
OPEN p_out_cur FOR lv_str;
END IF;
END sp_test;
Tool used: SQL developer(18c)
In dynamic SQL, you do NOT terminate statement with a semicolon.
EXECUTE IMMEDIATE 'SELECT * FROM '||lv_table_name||';';
-----
remove this
Anyway, you won't get any result when you run that piece of code. If you really want to see table's contents, you'll have to switch to something else, e.g. create a function that returns ref cursor.
Sample data:
SQL> SELECT * FROM test_table;
COL1 COL2
---------- ----------
1 2
3 4
Procedure you wrote is now correct:
SQL> CREATE OR REPLACE PROCEDURE sp_test (iv_table_name IN VARCHAR2,
2 p_out_cur OUT SYS_REFCURSOR)
3 AS
4 lv_str VARCHAR2 (400);
5 lv_count NUMBER (1);
6 lv_table_name VARCHAR2 (255) := UPPER (iv_table_name);
7 BEGIN
8 lv_str := 'SELECT * FROM ' || lv_table_name;
9
10 SELECT COUNT (1)
11 INTO lv_count
12 FROM all_tables
13 WHERE table_name = lv_table_name;
14
15 IF lv_count = 0
16 THEN
17 DBMS_OUTPUT.put_line ('Table does not exist');
18 ELSE
19 OPEN p_out_cur FOR lv_str;
20 END IF;
21 END sp_test;
22 /
Procedure created.
Testing:
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 l_rc SYS_REFCURSOR;
3 l_col1 NUMBER (10);
4 l_col2 NUMBER (10);
5 BEGIN
6 sp_test ('TEST_TABLE', l_rc);
7
8 LOOP
9 FETCH l_rc INTO l_col1, l_col2;
10
11 EXIT WHEN l_rc%NOTFOUND;
12
13 DBMS_OUTPUT.put_line (l_col1 || ', ' || l_col2);
14 END LOOP;
15 END;
16 /
1, 2 --> contents of the
3, 4 --> TEST_TABLE
PL/SQL procedure successfully completed.
SQL>
A function (instead of a procedure with the OUT parameter):
SQL> CREATE OR REPLACE FUNCTION sf_test (iv_table_name IN VARCHAR2)
2 RETURN SYS_REFCURSOR
3 AS
4 lv_str VARCHAR2 (400);
5 lv_count NUMBER (1);
6 lv_table_name VARCHAR2 (255) := UPPER (iv_table_name);
7 l_rc SYS_REFCURSOR;
8 BEGIN
9 lv_str := 'SELECT * FROM ' || lv_table_name;
10
11 SELECT COUNT (1)
12 INTO lv_count
13 FROM all_tables
14 WHERE table_name = lv_table_name;
15
16 IF lv_count = 0
17 THEN
18 raise_application_error (-20000, 'Table does not exist');
19 ELSE
20 OPEN l_rc FOR lv_str;
21 END IF;
22
23 RETURN l_rc;
24 END sf_test;
25 /
Function created.
Testing:
SQL> SELECT sf_test ('liksajfla') FROM DUAL;
SELECT sf_test ('liksajfla') FROM DUAL
*
ERROR at line 1:
ORA-20000: Table does not exist
ORA-06512: at "SCOTT.SF_TEST", line 18
SQL> SELECT sf_test ('TEST_TABLE') FROM DUAL;
SF_TEST('TEST_TABLE'
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
COL1 COL2
---------- ----------
1 2
3 4
SQL>

how to join cursor returned from procedure with other tables in oracle

i have a procedure that give me a refcursor as out parameter and i have to join that data with other tables in my own package.
i try to fetch cursor in a variable of the same return type but get: PLS-00497: cannot mix between single row and multi-row (BULK) in INTO list
types defined in external package (i cannot alter it)
CURSOR CUR_MYTYPE IS SELECT f1... FROM xxx
TYPE MYTYPE IS REF_CURSOR RETURN CUR_MYTYPE%ROWTYPE
my own code:
result SCHEMA.MYTYPE;
cur sys_refcursor;
check VARCHAR2;
PKG.PROCEDURE_NAME(check,cur);
fetch cur bulk collect into result ;
select t.f1, t.f2, t.f3, o.f1, o.f2
from table(result ) t
inner join otherTable o
on o.f1 = t.f1
Are you sure you've got all that correct? You cannot bulk collect into "result" if result is defined as a refcursor, eg
SQL> declare
2 cursor cur_mytype is select * from emp;
3 type mytype is ref cursor return cur_mytype%rowtype;
4 cur sys_refcursor;
5 result mytype;
6 begin
7 open cur for select * from emp;
8 fetch cur bulk collect into result;
9 end;
10 /
fetch cur bulk collect into result;
*
ERROR at line 8:
ORA-06550: line 8, column 31:
PLS-00497: cannot mix between single row and multi-row (BULK) in INTO list
I am going to assume it is more like this:
SQL> declare
2 cursor cur_mytype is select * from emp;
3 type mytype is table of cur_mytype%rowtype index by pls_integer;
4
5 cur sys_refcursor;
6 result mytype;
7 begin
8 open cur for select * from emp;
9 fetch cur bulk collect into result;
10 end;
11 /
PL/SQL procedure successfully completed.
In any event, MYTYPE needs to be a nested table type (which it is not in the case above) so that you can run the TABLE() function around it. If that is not the case, then you could create your own local schema type which maps to the columns and then transfer the rows to that, eg
--
-- incorrect type
--
SQL> declare
2 cursor cur_mytype is select * from emp;
3 type mytype is table of cur_mytype%rowtype index by pls_integer;
4
5 cur sys_refcursor;
6 result mytype;
7 begin
8 open cur for select * from emp;
9 fetch cur bulk collect into result;
10
11 for i in ( select * from table(result) )
12 loop
13 null;
14 end loop;
15 end;
16 /
for i in ( select * from table(result) )
*
ERROR at line 11:
ORA-06550: line 11, column 34:
PLS-00382: expression is of wrong type
ORA-06550: line 11, column 28:
PL/SQL: ORA-22905: cannot access rows from a non-nested table item
ORA-06550: line 11, column 14:
PL/SQL: SQL Statement ignored
--
-- data copied to corrected type
--
SQL> create or replace type emp_row as object (
2 empno number(4)
3 ,ename varchar2(10)
4 ,job varchar2(9)
5 ,mgr number(4)
6 ,hiredate date
7 ,sal number(7,2)
8 ,comm number(7,2)
9 ,deptno number(2)
10 );
11 /
Type created.
SQL>
SQL> create or replace type my_fixed_type as table of emp_row;
2 /
Type created.
SQL> declare
2 cursor cur_mytype is select * from emp;
3 type mytype is table of cur_mytype%rowtype index by pls_integer;
4
5 cur sys_refcursor;
6 result mytype;
7
8 fixed_result my_fixed_type := my_fixed_type();
9
10 begin
11 open cur for select * from emp;
12 fetch cur bulk collect into result;
13
14 for i in 1 .. result.count loop
15 fixed_result.extend;
16 fixed_result(i) :=
17 emp_row(
18 result(i).empno,
19 result(i).ename,
20 result(i).job,
21 result(i).mgr,
22 result(i).hiredate,
23 result(i).sal,
24 result(i).comm,
25 result(i).deptno);
26 end loop;
27
28 for i in ( select * from table(fixed_result) )
29 loop
30 null;
31 end loop;
32 end;
33 /
PL/SQL procedure successfully completed.

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>

Need to find the number of times PLSQL function gets executed

There is a requirement from client side that after function get executed more than 2 times then its output should be concatinated with some string and this function is inside a package .
for example...
function get called 7 times from package (its a backend job which executed automatically) and returns 'abc' but when the job runs for the 3rd time i want output 'abcde'.
One option is to create a separate log table and insert a row for each of function calls; then - within a function - check how many times it was invoked and return appropriate output. Something like this:
Log table:
SQL> CREATE TABLE flog
2 (
3 cuser VARCHAR2 (30),
4 sid NUMBER
5 );
Table created.
Package:
SQL> CREATE OR REPLACE PACKAGE pkg_test
2 IS
3 FUNCTION f_test
4 RETURN VARCHAR2;
5
6 PROCEDURE p_test;
7 END;
8 /
Package created.
Package body:
SQL> CREATE OR REPLACE PACKAGE BODY pkg_test
2 IS
3 FUNCTION f_test
4 RETURN VARCHAR2
5 IS
6 l_cnt NUMBER;
7 retval VARCHAR2 (10);
8 BEGIN
9 SELECT COUNT (*)
10 INTO l_cnt
11 FROM flog
12 WHERE cuser = USER
13 AND sid = SYS_CONTEXT ('USERENV', 'SID');
14
15 retval := CASE WHEN l_cnt <= 2 THEN 'abc' ELSE 'abc' || 'de' END;
16 RETURN retval;
17 END;
18
19 PROCEDURE p_test
20 IS
21 BEGIN
22 FOR i IN 1 .. 3
23 LOOP
24 INSERT INTO flog (cuser, sid)
25 VALUES (USER, SYS_CONTEXT ('USERENV', 'SID'));
26
27 DBMS_OUTPUT.put_line ('Execution #' || i || ', result = ' || f_test);
28 END LOOP;
29 END;
30 END pkg_test;
31 /
Package body created.
Testing:
SQL> SET SERVEROUTPUT ON
SQL> EXEC pkg_test.p_test;
Execution #1, result = abc
Execution #2, result = abc
Execution #3, result = abcde
PL/SQL procedure successfully completed.
SQL>

How to update ref cursor values in oracle?

I want to fetch limited no. of rows using refcursor. then I need to update same set of records. is it possible?
create or replace PROCEDURE myproc (
P_ROWCOUNT IN NUMBER,
OUT_TXN_IDS OUT OF_CR_TYPE,
P_CD_ERROR OUT NUMBER,
P_DS_ERROR OUT VARCHAR2
)
AS
V_TXNID NUMBER;
BEGIN
P_CD_ERROR := 0;
P_DS_ERROR := 'SUCCESS';
OPEN OUT_TXN_IDS for
SELECT id FROM table1 WHERE status='N' AND ROWNUM<=P_ROWCOUNT;
EXCEPTION
WHEN OTHERS THEN
P_CD_ERROR := sqlcode;
P_DS_ERROR := 'WF-ERROR - myproc - ' || substr(SQLERRM, 1, 200);
RETURN;
END myproc;
I need to update same records to status Y after refcursor returns. can we do this. please suggest
I don't have your tables nor data so I simplified it a little bit, but - it should work nonetheless.
Initial statuses:
SQL> SELECT status, count(*) FROM table1 group by status;
S COUNT(*)
- ----------
Y 7
N 7
Procedure: basically, you'd modify rows represented by ID returned by ref cursor.
SQL> DECLARE
2 out_txn_ids SYS_REFCURSOR;
3 p_rowcount NUMBER := 5;
4 l_id table1.id%TYPE;
5 BEGIN
6 OPEN out_txn_ids FOR SELECT id
7 FROM table1
8 WHERE status = 'N'
9 AND ROWNUM <= p_rowcount;
10
11 LOOP
12 FETCH out_txn_ids INTO l_id;
13
14 EXIT WHEN out_txn_ids%NOTFOUND;
15
16 UPDATE table1
17 SET status = 'Y'
18 WHERE id = l_id;
19 END LOOP;
20
21 CLOSE out_txn_ids;
22 END;
23 /
PL/SQL procedure successfully completed.
Result:
SQL> SELECT status, count(*) FROM table1 group by status;
S COUNT(*)
- ----------
Y 12
N 2
SQL>

Resources