Passing cursor record to a function - oracle

I have a stored procedure. I want to call a function from it. Want to pass the retrieved cursor record to the function. how can i pass the retrieved cursor record as function argument and how can i access it inside the function? How do i declare the function?
CREATE OR REPLACE PROCEDURE service__update as
cursor c_getData is
select *
from service_1
where status=5 ;
begin
dbms_output.enable(null);
for rec in c_getData loop
function(rec)

Assuming that you really want a function (which implies that you want to return a value) rather than a procedure (which does not return a value) and assuming that your cursor really is selecting every column from a single table, you can declare a function that takes an anchored %ROWTYPE
SQL> create function get_empno( p_rec in emp%rowtype )
2 return number
3 is
4 begin
5 return p_rec.empno;
6 end;
7 /
Function created.
and then call that function from your procedure
SQL> declare
2 l_empno emp.empno%type;
3 begin
4 for i in (select * from emp)
5 loop
6 l_empno := get_empno( i );
7 dbms_output.put_line( l_empno );
8 end loop;
9 end;
10 /
7369
7499
7521
7566
7654
7698
7782
7788
7839
7844
7876
7900
7902
7934
PL/SQL procedure successfully completed.

Related

Insert into table collection type in table function without using explicit cursor in PL/SQL

I write the following code in PL/SQL and it works:
declare
type deliveryStat_o IS record (
warehouseName varchar2(20), shipMode char(30), thirty_days number, sixty_days number,
ninety_days number, oneTwenty_days number, veryLate number
);
type deliveryStat_t is TABLE OF deliveryStat_o;
statTable deliveryStat_t;
begin
SELECT w_warehouse_name, sm_type, 1 AS thirty_days, 1 AS sixty_days, 1 AS ninety_days,
1 AS oneTwenty_days, 1 AS veryLateDelivery
bulk collect into statTable
FROM catalog_sales, warehouse, ship_mode, date_dim
WHERE cs_ship_date_sk = d_date_sk
AND cs_warehouse_sk = w_warehouse_sk
AND cs_ship_mode_sk = sm_ship_mode_sk
GROUP BY w_warehouse_name,
sm_type ;
end;
How can I do this inside a table function that returns the nested collection statTable. I understand that this can probably be accomplished using explicit cursors; however, is it possible to do it without using a cursor?
For context, I'm starting with this as the base
SQL> set serverout on
SQL> declare
2 type deliveryStat_o IS record (
3 empno number, ename varchar2(20)
4 );
5 type deliveryStat_t is TABLE OF deliveryStat_o;
6
7 statTable deliveryStat_t;
8
9 begin
10
11 SELECT empno, ename
12 bulk collect into statTable
13 FROM emp;
14 dbms_output.put_line('recs='||statTable.count);
15 end;
16 /
recs=14
PL/SQL procedure successfully completed.
To convert that to allow a table function, then those types need to be SQL types, hence
SQL> create or replace
2 type deliveryStat_o as object (
3 empno number, ename varchar2(20)
4 );
5 /
Type created.
SQL> create or replace
2 type deliveryStat_t as table of deliveryStat_o
3 /
Type created.
Now that this is done, the query must return a table of objects, so
SQL> set serverout on
SQL> declare
2 statTable deliveryStat_t;
3 begin
4
5 SELECT deliveryStat_o(empno, ename)
6 bulk collect into statTable
7 FROM emp;
8 dbms_output.put_line('recs='||statTable.count);
9 end;
10 /
recs=14
PL/SQL procedure successfully completed.
which can now be easily converted to a table function
SQL> create or replace
2 function my_func return deliveryStat_t is
3 statTable deliveryStat_t;
4 begin
5
6 SELECT deliveryStat_o(empno, ename)
7 bulk collect into statTable
8 FROM emp;
9 return statTable;
10 end;
11 /
Function created.
SQL> select * from my_func();
EMPNO ENAME
---------- --------------------
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
7698 BLAKE
7782 CLARK
7788 SCOTT
7839 KING
7844 TURNER
7876 ADAMS
7900 JAMES
7902 FORD
7934 MILLER
14 rows selected.
If you're returning a LOT of rows, then consider a pipelined function instead to avoid the memory overhead of collecting all the rows into the nested table

Is it possible to pass a column as a parameter to a ODER BY clause?

I want to use a function in Oracle. But I need to ORDER BY accordingly with what the user pass as parameter.
Example:
FUNCTION foo_function(p_date IN DATE, p_column_number IN NUMBER)
RETURN foo_bar
IS
BEGIN
SELECT * FROM bar WHERE date = p_date ORDER BY p_column_number
<...other code...>
END;
This block does not work.
Is it possible to do something like this to pass the column as parameter to the ORDER BY clause?
UPDATE ----------
#XING shows a very good way to solve the problem without DECODE like the answers of other questions.
But the problem is that I'm missing something.
Now I'm getting this error: "inconsistent datatypes: expected %s got %s"
-- Creating my object with 2 columns.
CREATE OR REPLACE TYPE MY_OBJECT AS OBJECT(IDOP NUMBER, EMISSION_DATE DATE);
-- Creating the table of MY_OBJECT type.
CREATE OR REPLACE TYPE TB_OBJECT AS TABLE OF MY_OBJECT;
-- Creating my function
CREATE OR REPLACE FUNCTION GET_OBJECT(
P_INITIAL_DATE IN DATE,
P_FINAL_DATE IN DATE,
P_COLUMN_NUMBER IN NUMBER)
RETURN TB_OBJECT
IS
V_TB TB_OBJECT;
V_SQL VARCHAR2(1000);
BEGIN
V_SQL :=
'SELECT IDOP, EMISSION_DATE FROM OP WHERE EMISSION_DATE BETWEEN :p_initial_date AND :p_final_date ORDER BY :p_column_number';
EXECUTE IMMEDIATE V_SQL
BULK COLLECT INTO V_TB
USING P_COLUMN_NUMBER;
RETURN (V_TB);
END;
-- Calling the function
SELECT * FROM TABLE (GET_OBJECT(TO_DATE('01/06/2017','dd/MM/yyyy'), TO_DATE('09/01/2018', 'dd/MM/yyyy'), 1));
UPDATE (2) ---------------------
The problem in my code is that I forget to cast the select to my object.
This way the code works.
V_SQL :=
'SELECT MY_OBJECT(IDOP, EMISSION_DATE) FROM OP WHERE EMISSION_DATE BETWEEN :p_initial_date AND :p_final_date ORDER BY :p_column_number';
Thank you #XING
Here in the below example i show you how to do that in a function by only ordering only 1 column. You can modify the function as per your need;
--Created a type of number to return the ordered result. You can create an object with the column same as used in your select statement.
CREATE OR REPLACE TYPE VAR_RET AS TABLE OF NUMBER;
/
--Function
CREATE OR REPLACE FUNCTION FOO_FUNCTION (
P_COLUMN_NUMBER IN NUMBER)
RETURN VAR_RET
IS
V_RES VAR_RET;
v_sql VARCHAR2(1000);
BEGIN
V_SQL :=
'SELECT EMPNO FROM EMP ORDER BY :p_column_number'; --<-- Create Object with all the columns you are selecting here and then a type of your object to hold result
EXECUTE IMMEDIATE V_SQL
BULK COLLECT INTO V_RES
USING P_COLUMN_NUMBER;
RETURN (V_RES);
END;
--Result:
SQL> select * from table( FOO_FUNCTION (1));
COLUMN_VALUE
------------
7369
7499
7521
7566
7654
7698
7782
7788
7839
7844
7876
EDIT: WITH 2 COLUMNS Ordering
CREATE OR REPLACE TYPE MY_TAB AS OBJECT
(
V_EMPNM VARCHAR2(100),
V_EMPNO NUMBER
);
CREATE OR REPLACE TYPE VAR_RET AS TABLE OF MY_TAB;
/
CREATE OR REPLACE FUNCTION FOO_FUNCTION (
P_COLUMN_NUMBER IN NUMBER)
RETURN VAR_RET
IS
V_RES VAR_RET;
v_sql VARCHAR2(1000);
BEGIN
V_SQL := --You need to cast your select statement as per Object
'SELECT MY_TAB(ENAME,EMPNO) FROM EMP ORDER BY :p_column_number'; --<-- Create Object with all the columns you are selecting here and then a type of your object to hold result
EXECUTE IMMEDIATE V_SQL
BULK COLLECT INTO V_RES
USING P_COLUMN_NUMBER;
RETURN (V_RES);
END;
Execution:
1) Way 1
DECLARE
VAR VAR_RET := VAR_RET ();
BEGIN
VAR := FOO_FUNCTION (1);
FOR I IN 1 .. VAR.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE (VAR (I).V_EMPNM ||' ' ||VAR (I).V_EMPNO);
END LOOP;
END;
SQL> /
SMITH 7369
ALLEN 7499
WARD 7521
JONES 7566
MARTIN 7654
BLAKE 7698
CLARK 7782
SCOTT 7788
KING 7839
TURNER 7844
ADAMS 7876
JAMES 7900
FORD 7902
MILLER 7934
PL/SQL procedure successfully completed.
2) WAY 2
select * from table( FOO_FUNCTION (1));
V_EMPNM V_EMPNO
----------------------------------------------------------------------------------------------------
SMITH 7369
ALLEN 7499
WARD 7521
JONES 7566
MARTIN 7654
BLAKE 7698
CLARK 7782
SCOTT 7788
KING 7839
TURNER 7844
ADAMS 7876
V_EMPNM V_EMPNO
----------------------------------------------------------------------------------------------------
JAMES 7900
FORD 7902
MILLER 7934
14 rows selected.

Oracle PL/SQL collect values from a loop into a cursor

I have a PL/SQL TABLE TYPE result set that contains document ids.
I can loop over the result set without a problem, but the issue is that I have to return a sys_refcursor from the function, but I am unable to collect the values from the loop into the cursor.
TYPE table_typ IS TABLE OF DOCUMENT_QUEUE.ENV_ID%TYPE INDEX BY PLS_INTEGER;
FUNCTION GET_DOCS()
RETURN SYS_REFCURSOR
IS
LS_CUR SYS_REFCURSOR;
LR_UPDATED_ROWS table_typ;
BEGIN
UPDATE DOCUMENT_QUEUE DQ
...
RETURNING DQ.ENV_ID BULK COLLECT INTO LR_UPDATED_ROWS;
-- Need to collect all of the following rows into the cursor
FOR indx IN NVL (LR_UPDATED_ROWS.FIRST, 0) .. NVL (LR_UPDATED_ROWS.LAST, -1)
LOOP
SELECT * FROM DOCUMENT_QUEUE DQ WHERE DQ.ENV_ID = LR_UPDATED_ROWS(indx);
END LOOP;
RETURN LS_CUR;
END GET_DOCS;
All help and hints are welcome.
FOR indx IN NVL (LR_UPDATED_ROWS.FIRST, 0) .. NVL (LR_UPDATED_ROWS.LAST, -1)
LOOP
SELECT * FROM DOCUMENT_QUEUE DQ WHERE DQ.ENV_ID = LR_UPDATED_ROWS(indx);
END LOOP;
RETURN LS_CUR;
You do not need the cursor FOR LOOP. You could use OPEN CURSOR FOR statement and return a SYS_REFCURSOR.
For example,
OPEN LS_CUR FOR SELECT * FROM DOCUMENT_QUEUE DQ
WHERE DQ.ENV_ID IN (SELECT * FROM TABLE(LR_UPDATED_ROWS));
RETURN LS_CUR;
or,
OPEN LS_CUR FOR SELECT * FROM DOCUMENT_QUEUE DQ
WHERE DQ.ENV_ID MEMBER OF LR_UPDATED_ROWS;
RETURN LS_CUR;
However, in order to do that, you must CREATE the type at SQL level not at PL/SQL level. Else, you would receive PLS-00642: local collection types not allowed in SQL statements.
A small demo:
Create the type at SQL level:
SQL> CREATE OR REPLACE TYPE table_typ AS TABLE OF NUMBER
2 /
Type created.
Let's get the output in SQL*Plus using a refcursor:
Using MEMBER OF syntax:
SQL> variable r refcursor
SQL> DECLARE
2 l_typ table_typ;
3 TYPE numbers IS TABLE OF NUMBER;
4 n numbers;
5 BEGIN
6 SELECT empno BULK COLLECT INTO l_typ FROM emp;
7 OPEN :r FOR SELECT empno,
8 ename FROM emp WHERE empno member OF l_typ;
9 END;
10 /
PL/SQL procedure successfully completed.
SQL> print r
EMPNO ENAME
---------- ----------
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
7698 BLAKE
7782 CLARK
7788 SCOTT
7839 KING
7844 TURNER
7876 ADAMS
7900 JAMES
7902 FORD
7934 MILLER
14 rows selected.
Using TABLE function:
SQL> variable r refcursor
SQL> DECLARE
2 l_typ table_typ;
3 TYPE numbers IS TABLE OF NUMBER;
4 n numbers;
5 BEGIN
6 SELECT empno BULK COLLECT INTO l_typ FROM emp;
7 OPEN :r FOR SELECT empno,
8 ename FROM emp WHERE empno IN (SELECT * from TABLE(l_typ));
9 END;
10 /
PL/SQL procedure successfully completed.
SQL> print r
EMPNO ENAME
---------- ----------
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
7698 BLAKE
7782 CLARK
7788 SCOTT
7839 KING
7844 TURNER
7876 ADAMS
7900 JAMES
7902 FORD
7934 MILLER
14 rows selected.
For the mentioned requirement i have mentioned below a snippet which will help to fetch all the rows into ref cursor for every rowid. Let me know if this helps.
CREATE OR REPLACE TYPE table_typ
IS
TABLE OF DOCUMENT_QUEUE.ENV_ID%TYPE INDEX BY PLS_INTEGER;
CREATE OR REPLACE FUNCTION GET_DOCS
RETURN SYS_REFCURSOR
IS
LS_CUR SYS_REFCURSOR;
LR_UPDATED_ROWS table_typ;
lv_rows_lst VARCHAR2(32676);
BEGIN
SELECT <COL1> BULK COLLECT INTO LR_UPDATED_ROWS FROM <TABLE_NAME>;
FOR I IN LR_UPDATED_ROWS.FIRST..LR_UPDATED_ROWS.LAST
LOOP
lv_rows_lst:=lv_rows_lst||','||LR_UPDATED_ROWS(I);
END LOOP;
lv_rows_lst:=SUBSTR(lv_rows_lst,2,LENGTH(lv_rows_lst));
OPEN LS_CUR FOR 'SELECT * FROM DOCUMENT_QUEUE DQ WHERE DQ.ENV_ID IN ('||lv_rows_lst||')';
RETURN LS_CUR;
END GET_DOCS;

PLSQL select query in BEGIN block

CREATE OR REPLACE PROCEDURE validate_date
AS
strABC DATE;
strDummy VARCHAR2(20);
CURSOR C_Example IS SELECT * from dummytable;
BEGIN
FOR R_Example IN C_RetNxWeek LOOP
strABC := R_RetNxWeek.something;
strDummy := R_RetNxWeek.something;
END LOOP;
IF (something < something) THEN
ELSE
strDummy:= SELECT * FROM sometable WHERE something = something; <-----alternative to do this?
END IF;
END validate_date;
I have a very basic store procedure template like above, i have a cursor that will select some record from a table, in the IF ELSE statement in BEGIN block, i wish to do a checking on a data by selecting a table, since i cannot put cursor in BEGIN block, how can i do that?
The select statement result cannot be stored as you stated,
it should be done via
It should use INTO Clause please refer https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/returninginto_clause.htm
No need of an explicit cursor, use a CURSOR FOR LOOP. I don't know your requirement, since I think it could be done using plain SQL. However, if you really want to use PL/SQL, you can make use of BULK COLLECT and FORALL.
And regarding the SELECT, you need to use INTO clause in PL/SQL.
SQL> DECLARE
2 eno NUMBER;
3 STR_DUMMY VARCHAR2(20);
4 BEGIN
5 FOR i IN
6 (SELECT * FROM emp
7 )
8 LOOP
9 eno := i.empno;
10 dbms_output.put_line(eno);
11 END LOOP;
12 SELECT ENAME INTO STR_DUMMY FROM EMP WHERE EMPNO = 7788;
13 dbms_output.put_line(STR_DUMMY);
14 END;
15 /
7369
7499
7521
7566
7654
7698
7782
7788
7839
7844
7876
7900
7902
7934
SCOTT
PL/SQL procedure successfully completed.
SQL>

Get resultset from oracle stored procedure

I'm working on converting a stored procedure from SQL server to Oracle.
This stored procedure provides a direct resultset. I mean that if you call the stored procedure in eg Management Studio you directly obtain the resultset.
By converting to Oracle I walk against the problem that I in Oracle will not display the resultset
I searched on the Internet and have seen that the stored procedure should yield a REF CURSOR, but I still walk with the problem to write a little piece of code to obtain the resultset en process that.
Pseudo Code:
Call stored procedure and obtain cursor
Do something with that cursor so that my resultset appears
Someone an idea?
In SQL Plus:
SQL> create procedure myproc (prc out sys_refcursor)
2 is
3 begin
4 open prc for select * from emp;
5 end;
6 /
Procedure created.
SQL> var rc refcursor
SQL> execute myproc(:rc)
PL/SQL procedure successfully completed.
SQL> print rc
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- ----------- ---------- ---------- ----------
7839 KING PRESIDENT 17-NOV-1981 4999 10
7698 BLAKE MANAGER 7839 01-MAY-1981 2849 30
7782 CLARKE MANAGER 7839 09-JUN-1981 2449 10
7566 JONES MANAGER 7839 02-APR-1981 2974 20
7788 SCOTT ANALYST 7566 09-DEC-1982 2999 20
7902 FORD ANALYST 7566 03-DEC-1981 2999 20
7369 SMITHY CLERK 7902 17-DEC-1980 9988 11 20
7499 ALLEN SALESMAN 7698 20-FEB-1981 1599 3009 30
7521 WARDS SALESMAN 7698 22-FEB-1981 1249 551 30
7654 MARTIN SALESMAN 7698 28-SEP-1981 1249 1400 30
7844 TURNER SALESMAN 7698 08-SEP-1981 1499 0 30
7876 ADAMS CLERK 7788 12-JAN-1983 1099 20
7900 JAMES CLERK 7698 03-DEC-1981 949 30
7934 MILLER CLERK 7782 23-JAN-1982 1299 10
6668 Umberto CLERK 7566 11-JUN-2009 19999 0 10
9567 ALLBRIGHT ANALYST 7788 02-JUN-2009 76999 24 10
Oracle is not sql server. Try the following in SQL Developer
variable rc refcursor;
exec testproc(:rc2);
print rc2
My solution was to create a pipelined function. The advantages are that the query can be a single line:
select * from table(yourfunction(param1, param2));
You can join your results to other tables or filter or sort them as you please..
the results appear as regular query results so you can easily manipulate them.
To define the function you would need to do something like the following:
-- Declare the record columns
TYPE your_record IS RECORD(
my_col1 VARCHAR2(50),
my_col2 varchar2(4000)
);
TYPE your_results IS TABLE OF your_record;
-- Declare the function
function yourfunction(a_Param1 varchar2, a_Param2 varchar2)
return your_results pipelined is
rt your_results;
begin
-- Your query to load the table type
select s.col1,s.col2
bulk collect into rt
from your_table s
where lower(s.col1) like lower('%'||a_Param1||'%');
-- Stuff the results into the pipeline..
if rt.count > 0 then
for i in rt.FIRST .. rt.LAST loop
pipe row (rt(i));
end loop;
end if;
-- Add more results as you please....
return;
end find;
And as mentioned above, all you would do to view your results is:
select * from table(yourfunction(param1, param2)) t order by t.my_col1;
Hi I know this was asked a while ago but I've just figured this out and it might help someone else. Not sure if this is exactly what you're looking for but this is how I call a stored proc and view the output using SQL Developer.
In SQL Developer when viewing the proc, right click and choose 'Run' or select Ctrl+F11 to bring up the Run PL/SQL window. This creates a template with the input and output params which you need to modify. My proc returns a sys_refcursor. The tricky part for me was declaring a row type that is exactly equivalent to the select stmt / sys_refcursor being returned by the proc:
DECLARE
P_CAE_SEC_ID_N NUMBER;
P_FM_SEC_CODE_C VARCHAR2(200);
P_PAGE_INDEX NUMBER;
P_PAGE_SIZE NUMBER;
v_Return sys_refcursor;
type t_row is record (CAE_SEC_ID NUMBER,FM_SEC_CODE VARCHAR2(7),rownum number, v_total_count number);
v_rec t_row;
BEGIN
P_CAE_SEC_ID_N := NULL;
P_FM_SEC_CODE_C := NULL;
P_PAGE_INDEX := 0;
P_PAGE_SIZE := 25;
CAE_FOF_SECURITY_PKG.GET_LIST_FOF_SECURITY(
P_CAE_SEC_ID_N => P_CAE_SEC_ID_N,
P_FM_SEC_CODE_C => P_FM_SEC_CODE_C,
P_PAGE_INDEX => P_PAGE_INDEX,
P_PAGE_SIZE => P_PAGE_SIZE,
P_FOF_SEC_REFCUR => v_Return
);
-- Modify the code to output the variable
-- DBMS_OUTPUT.PUT_LINE('P_FOF_SEC_REFCUR = ');
loop
fetch v_Return into v_rec;
exit when v_Return%notfound;
DBMS_OUTPUT.PUT_LINE('sec_id = ' || v_rec.CAE_SEC_ID || 'sec code = ' ||v_rec.FM_SEC_CODE);
end loop;
END;
In SQL Plus:
SQL> var r refcursor
SQL> set autoprint on
SQL> exec :r := function_returning_refcursor();
Replace the last line with a call to your procedure / function and the contents of the refcursor will be displayed
FYI as of Oracle 12c, you can do this:
CREATE OR REPLACE PROCEDURE testproc(n number)
AS
cur SYS_REFCURSOR;
BEGIN
OPEN cur FOR SELECT object_id,object_name from all_objects where rownum < n;
DBMS_SQL.RETURN_RESULT(cur);
END;
/
EXEC testproc(3);
OBJECT_ID OBJECT_NAME
---------- ------------
100 ORA$BASE
116 DUAL
This was supposed to get closer to other databases, and ease migrations.
But it's not perfect to me, for instance SQL developer won't display it nicely as a normal SELECT.
I prefer the output of pipeline functions, but they need more boilerplate to code.
more info:
https://oracle-base.com/articles/12c/implicit-statement-results-12cr1
CREATE OR REPLACE PROCEDURE SP_Invoices(p_nameClient IN CHAR)
AS
BEGIN
FOR c_invoice IN
(
SELECT CodeInvoice, NameClient FROM Invoice
WHERE NameClient = p_nameClient
)
LOOP
dbms_output.put_line('Code Invoice: ' || c_invoice.CodeInvoice);
dbms_output.put_line('Name Client : ' || c_invoice.NameClient );
END LOOP;
END;
Executing in SQL Developer:
BEGIN
SP_Invoices('Perico de los palotes');
END;
-- Or:
EXEC SP_Invoices('Perico de los palotes');
Output:
> Code Invoice: 1
> Name Client : Perico de los palotes
> Code Invoice: 2
> Name Client : Perico de los palotes

Resources