How to return a cursor populated with NESTED RECORD type from Oracle procedure - sql

I need to return a cursor from Oracle procedure, the cursor has to contain RECORD types. I know how to solve this problem when the RECORD is simple, but how can this be solved for NESTED RECORDS?
There is a working block of code for simple RECORD:
-- create package with a RECORD type
create or replace package pkg as
-- a record contains only one simple attribute
type t_rec is RECORD (
simple_attr number
);
end;
/
-- create a testing procedure
-- it returns a cursor populated with pkg.t_rec records
create or replace procedure test_it(ret OUT SYS_REFCURSOR) is
type cur_t is ref cursor return pkg.t_rec;
cur cur_t;
begin
-- this is critical; it is easy to populate simple RECORD type,
-- because select result is mapped transparently to the RECORD elements
open cur for select 1 from dual;
ret := cur; -- assign the cursor to the OUT parameter
end;
/
-- and now test it
-- it will print one number (1) to the output
declare
refcur SYS_REFCURSOR;
r pkg.t_rec;
begin
-- call a procedure to initialize cursor
test_it(refcur);
-- print out cursor elements
loop
fetch refcur into r;
exit when refcur%notfound;
dbms_output.put_line(r.simple_attr);
end loop;
close refcur;
end;
/
Can you show me, how it could be done when a RECORD t_rec contains NESTED RECORD?
Modify the example in the folowing way:
-- create package with a NESTED RECORD type
create or replace package pkg as
type t_rec_nested is RECORD (
nested_attr number
);
-- a record with NESTED RECORD
type t_rec is RECORD (
simple_attr number,
nested_rec t_rec_nested
);
end;
/
create or replace procedure test_it(ret OUT SYS_REFCURSOR) is
type cur_t is ref cursor return pkg.t_rec;
cur cur_t;
begin
-- how to populate a result?
open cur for ????
ret := cur;
end;
/
The question is how to modify test_it procedure to populate a cursor?
I spent many hours searching the solution, I will appreciate any help.

I don't think it's possible as you have it, as RECORDs are a PL/SQL data type. You can do an equivalent thing by making an OBJECT. If you scroll down to the bottom of this link (or search for "Updating a Row Using a Record Containing an Object: Example" on the page), you will see how that is handled.

Related

Oracle procedure with SYS_REFCURSOR as OUT parameter displays only old values

I created the following table called APP_USERS with nine columns:
I want to get all data from a single record by calling a procedure with SYS_REFCURSOR as OUT parameter: The code looks like that:
create or replace PACKAGE BODY USER_LOGIN AS
PROCEDURE getUserToLogIn(comp_id NUMBER, user_id APP_USERS.USER_ID%TYPE, pass APP_USERS.ACC_PASSWORD%TYPE, v_IS_ADMIN IN char,
curs out sys_refcursor)
IS
lala APP_USERS%ROWTYPE;
BEGIN
OPEN curs FOR
SELECT USER_ID,FIRST_NAME,MIDDLE_NAME,LAST_NAME,SEX,COMPANY_ID,IS_ADMIN,ACC_PASSWORD,IS_ACTIVE
FROM APP_USERS
WHERE COMPANY_ID = comp_id and USER_ID = TRIM(user_id) and ACC_PASSWORD = pass and IS_ADMIN = v_IS_ADMIN;
DBMS_OUTPUT.PUT_LINE(comp_id);
DBMS_OUTPUT.PUT_LINE(user_id);
DBMS_OUTPUT.PUT_LINE(pass);
DBMS_OUTPUT.PUT_LINE(v_IS_ADMIN);
DBMS_OUTPUT.PUT_LINE('--------------------------------------');
LOOP
FETCH curs INTO lala;
exit when curs%notfound;
DBMS_OUTPUT.PUT_LINE(lala.USER_ID);
DBMS_OUTPUT.PUT_LINE(lala.COMPANY_ID);
DBMS_OUTPUT.PUT_LINE(lala.ACC_PASSWORD);
DBMS_OUTPUT.PUT_LINE(lala.IS_ADMIN);
END LOOP;
CLOSE curs;
END getUserToLogIn;
END USER_LOGIN;
At first I called a procedure by running a simple anonymous block like below:
declare
curs sys_refcursor;
begin
USER_LOGIN.getUserToLogIn(1000,'EU1002','UCFE3M0N','N',curs);
end;
and I got the following results:
Next time I run the anonymous block again with wrong data, so that the procedure could not find them, but the result was not correct:
At first time, when I entered correct data my procedure displayed them, but the second attempt with wrong data entered caused displaying the data from the previous one.
My question is why:
exit when curs%notfound;
is this part of the code overlooked?
That not the proper way to use a refcursor. Here is a simplified example from your code:
create or replace PROCEDURE getUserToLogIn( user_id APP_USERS.USER_ID%TYPE,
curs in out sys_refcursor)
IS
BEGIN
OPEN curs FOR SELECT * FROM APP_USERS; -- where blah, blah
end ;
declare
lala APP_USERS%ROWTYPE;
my_cursor sys_refcursor;
begin
getUserToLogIn('xxx', my_cursor);
LOOP
FETCH my_cursor INTO lala;
exit when my_cursor%notfound;
DBMS_OUTPUT.PUT_LINE('output= ' || lala.USER_ID);
end loop;
close my_cursor;
end;
So the standard method is you call the procedure that creates the SELECT statement you need, then just OPEN cursor parameter. The caller then gets the cursor back, iterates thought it, then closes it.
Heres also a tutorial.

Print multiple rows from select statement

I have this table :
| Pattern |
----------------------
|category |varchar|
|patternexpr |varchar|
For example in this table I can have a category ISBN and its pattern to recognize it.
I want to create a procedure which takes three arguments : a table T, one of its column C and a category. I want to print every rows in column C in T table which respect the pattern associated.
This is what I did (Updated with the correct answer):
CREATE OR REPLACE PROCEDURE Recognize(T varchar,C varchar,catego varchar)
IS
v_patt Pattern.CATEGOR%Type;
BEGIN
SELECT patternexpr INTO v_patt
FROM Pattern WHERE CATEGOR=catego;
FOR myrow IN (SELECT C FROM T WHERE REGEXP_LIKE(C, v_patt) LOOP
dbms_output.put_line(myrow.C);
END LOOP;
END;
/
How can I declare a cursor to print my result without knowing the value of my variable patt in the "DECLARE" place ? Should I add another declare and begin...end bloc after the first query ? What is the best way to do it ?
(I'm working on Oracle SGBD)
Use REF CURSOR to fetch records for this purpose.
CREATE OR REPLACE PROCEDURE Recognize(
T VARCHAR2,
C VARCHAR2,
catego VARCHAR2)
IS
v_patt Pattern.CATEGOR%Type;
v_cur_txt VARCHAR2(400);
TYPE cur_type
IS
REF
CURSOR;
v_cur cur_type;
v_c VARCHAR2(20);
BEGIN
SELECT patternexpr INTO v_patt FROM Pattern WHERE CATEGOR=catego;
v_cur_txt := 'SELECT '||C||' FROM '|| T ||' WHERE REGEXP_LIKE('||C||', '''||v_patt||''')';
OPEN v_cur FOR v_cur_txt;
LOOP
FETCH v_cur INTO v_c;
EXIT
WHEN v_cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_c);
END LOOP;
CLOSE v_cur;
END;
/
NOTE: : Include proper EXCEPTION handling in your code for NO_DATA_FOUND etc.Also as per Nicholas , make some validations by using dbms_assert package
In Oracle, you don't need an explicit cursor:
for myrow in (select c from t where regexp_like(c, patt) loop
dbms_output.put_line(myrow.c);
end loop;
I would call the pattern variable something like v_patt; that way, declared variables don't get confused with column names.

How to edit a cursor attribute for a return statement on plsql?

I want to return a cursor from a function, I have read that I can use:
return sys_refcursor
And then
open curs for select* from mytable;
return curs;
I tried curs.att := 'something' but I get an error
Also read I can do my own type:
TYPE type IS REF CURSOR RETURN mytable%ROWTYPE;
Then
CURSOR cur IS
SELECT* FROM mytable;
var cur%ROWTYPE;
BEGIN
OPEN cur;
FETCH cur INTO var;
var.att = 'something';
RETURN var;
This time I didn't get an error in the assign but in the return statement.
If I changed the var type to my type I couldn't fetch the value.
I wan't to edit the cursor, but not the table, how can I do this?
A cursor is a read-only structure. The only way to change the data that you would fetch from a cursor is to change the SQL statement that is used to open the cursor or to change the data in the underlying table(s).
While it is possible to return a cursor from one PL/SQL block to another, it is rarely the appropriate architecture. A SYS_REFCURSOR is generally appropriate when you want to return a result to a client application that knows how to use a cursor.
Do you really want to return a cursor, though? Or do you want to return a record type? The second code snippet you posted appears to be trying to return a record-- that's certainly possible but you would need to declare that the function returns a record rather than a cursor. That is, the RETURN statement in the declaration would need to be RETURN mytable%ROWTYPE rather than RETURN type. For example, if you want to return a record based on the EMP table
create or replace function get_emp( p_empno in emp.empno%type )
return emp%rowtype
is
l_rec emp%rowtype;
begin
select *
into l_rec
from emp
where empno = p_empno;
l_rec.sal := l_rec.sal + 100;
return l_rec;
end;

How can I fetch the data from the SYS_REFCURSOR from one stored proc and use it in another?

I have a stored proc with the basic layout below that returns a sys_refcursor as a result set. (Technically it reurns four but for the sake of clarity I just say one). The result set is a selection from a temp table.
procedure aProcedure
( C1 in out sys_refcursor
) is
begin
--populate Temp_Table here with a stored proc call;
OPEN C1 FOR
SELECT Cols
FROM TEMP_TABLE;
I need to insert this result set from C1 into a permanent table using a different stored procedure. Is this Do-able or do I need to re-build the result set all over again?
I've been able to find information on working with cursors and result sets outside of oracle but not for working with them within itself.
I know it might make sense to just do the insert from the first stored proc but that's not really how I need it to happen. It's an optional requirement to save the result set permanently.
Thanks for any helpful info.
Assuming that the caller knows the structure of the cursor that aProcedure is opening, you can do something like this.
declare
l_rc sys_refcursor;
l_rec temp_table%rowtype;
begin
aProcedure( l_rc );
loop
fetch l_rc
into l_rec;
exit when l_rc%notfound;
dbms_output.put_line( l_rec.col1 );
end loop;
close l_rc;
end;
/
If you can't fetch into a record type, you can also fetch into a number of other scalar local variables (the number and type have to match the number and type of columns that aProcedure specifies in its SELECT list). In my case, I defined aProcedure to return two numeric columns
declare
l_rc sys_refcursor;
l_col1 number;
l_col2 number;
begin
aProcedure( l_rc );
loop
fetch l_rc
into l_col1, l_col2;
exit when l_rc%notfound;
dbms_output.put_line( l_col1 );
end loop;
close l_rc;
end;

trouble defining weakly defined ref cursor

I'm attempting to write a stored proc that takes in a number, n, and returns the first n results for a given query, exclusively locking those n rows. I'm a little new to SQL and I'm having a bit of difficulty matching data types correctly.
My package spec looks like this:
PACKAGE package IS
Type out_result_type is REF CURSOR;
PROCEDURE stored_proc
(in_n IN NUMBER DEFAULT 10,
out_list IN OUT out_result_type);
I then define the cursor in the procedure body, like so:
CURSOR OUT_RESULT_TYPE IS
SELECT a.id
FROM schema.table a
WHERE (some conditions) AND rownum <= in_n;
A bit later on I then try to extract the results of the cursor into the output variable:
OPEN OUT_RESULT_TYPE;
FETCH OUT_RESULT_TYPE INTO out_list; -- error on this line
CLOSE OUT_RESULT_TYPE;
But alas this code doesn't compile; oracle complains that out_list has already been defined with a conflicting data type. Any idea how I can resolve this issue? It's driving me crazy!
Thanks in advance.
CREATE OR REPLACE PACKAGE pkg_test
AS
TYPE tt_cur IS REF CURSOR;
PROCEDURE prc_cur (retval OUT tt_cur);
END;
CREATE OR REPLACE PACKAGE BODY pkg_test
AS
PROCEDURE prc_cur (retval OUT tt_cur)
AS
BEGIN
OPEN retval
FOR
SELECT *
FROM dual;
END;
END;
If you want to lock, use:
CREATE OR REPLACE PACKAGE BODY pkg_test
AS
PROCEDURE prc_cur (retval OUT tt_cur)
AS
BEGIN
OPEN retval
FOR
SELECT a.id
FROM schema.table a
WHERE (some conditions)
AND rownum <= in_n
ORDER BY
column
-- Never forget ORDER BY!
FOR UPDATE;
END;
END;
Two remarks:
A cursor doesn't lock.
You don't have to do Type out_result_type is REF CURSOR;, use default type sys_refcursor. See here: Oracle - How to have an out ref cursor parameter in a stored procedure?
Your out_list must be of wrong type. Consider (script run on 10.2.0.3):
CREATE TABLE t AS SELECT ROWNUM ID FROM all_objects WHERE ROWNUM <= 100;
CREATE OR REPLACE PACKAGE cursor_pck AS
TYPE out_result_type is REF CURSOR;
PROCEDURE stored_proc (p_in IN NUMBER DEFAULT 10,
p_out_list IN OUT out_result_type);
END cursor_pck;
/
If you want to select and lock the rows at the same time you would use the FOR UPDATE clause:
CREATE OR REPLACE PACKAGE BODY cursor_pck AS
PROCEDURE stored_proc (p_in IN NUMBER DEFAULT 10,
p_out_list IN OUT out_result_type) IS
BEGIN
OPEN p_out_list FOR SELECT a.id FROM t a WHERE ROWNUM <= p_in FOR UPDATE;
END stored_proc;
END cursor_pck;
/
With the following setup, you will call the procedure like this:
SQL> SET SERVEROUTPUT ON;
SQL> DECLARE
2 l_cursor cursor_pck.out_result_type;
3 l_id t.id%TYPE;
4 BEGIN
5 cursor_pck.stored_proc(3, l_cursor);
6 LOOP
7 FETCH l_cursor INTO l_id;
8 EXIT WHEN l_cursor%NOTFOUND;
9 dbms_output.put_line(l_id);
10 END LOOP;
11 END;
12 /
1
2
3
PL/SQL procedure successfully completed
This is not going to work the way it's written, because
out_list expects a cursor, not a cursor result.
The name out_result_type is already used for a type, so you can't redefine it to be a cursor in the same scope.
Oracle provides a pre-defined weak reference cursor: sys_refcursor. In usage it would look like:
CREATE OR REPLACE PACKAGE pkg_test
AS
PROCEDURE prc_cur (p_retval OUT sys_refcursor,
p_lookup IN VARCHAR2);
END pkg_test;
CREATE OR REPLACE PACKAGE BODY pkg_test
AS
PROCEDURE prc_cur(p_retval OUT sys_refcursor
p_lookup IN VARCHAR2)
IS
BEGIN
OPEN retval FOR SELECT a.value
FROM tblname a
WHERE a.id <= p_lookup;
END prc_cur;
END pkg_test;
This saves you the trouble of needing to declare a type. The sys_refcursor is a pointer to a result set from an open cursor. If you are familiar with Java, it's the same concept as the java.sql.ResultSet object which provides a way to get at the results of a query.