oracle - out sys_refcursor parameter - if no result found then? - sql

If I use out sys_refcursor parameter, I'm struggling with if the first cursor has any results.
create or replace procedure cursorresults
as
(cursor1 OUT sys_refcursor
cursor2 OUT sys_Refcursor)
begin
open cursor1 for
select * from table1;
**if cursor1 has any results**
open cursor2 for
select * from table2;
end;
I can requery the table1 to see if there was a match but there must be a better way?

The only way to determine whether a cursor returns results is to attempt to fetch results from the cursor. Of course, since a cursor is a forward-only structure, that means that you would need to close and re-open the cursor.
It seems very odd, though, that you would only want to open the second cursor if the first cursor returns results. Are you sure that you don't want to join the two tables, union them, or do something else to combine the two results?

Related

How to make a function that takes code like "SELECT * FROM SOME_TABLE" as an input and returns a table as an output?

I want to create a function that takes some code as an input (e.g. Select * FROM SOME_TABLE) and returns the result of a query as an output.
I want to use it in procedures in order to return tables as a result.
It should look like this:
BEGIN
--some procedure code
CREATE TABLE SOME_TABLE as Select * FROM ...;
Select * FROM table(my_function('Select * FROM SOME_TABLE'));
END;
Important tips:
The resulting table can have multiple columns, from 1 to +inft
The resulting table can have multiple rows, from 1 to +inft
So the size of a table can be both very small or very large.
The input query can have several where, having, partition, and other Oracle constructions.
I want to have a table as an output, not DBMS_OUTPUT.
I can't install any modules/applications, or use other languages hints. However, I can manually create types, functions, procedures.
I tried to search in the net but could not find a solution that meets all my expectations. The best link I've found was this:
https://sqljana.wordpress.com/2017/01/22/oracle-return-select-statement-results-like-sql-server-sps-using-pipelined-functions/
DBMS_SQL.RETURN_RESULT works if your "code" is a select query
DECLARE
l_cur SYS_REFCURSOR;
l_query VARCHAR2(4000) := 'select * from SOME_TABLE';
BEGIN
OPEN l_cur for l_query;
DBMS_SQL.RETURN_RESULT(l_cur);
END;
/
you can create a function that has a string as parameter and return a cursor.
select statement you should pass as a string. in a function you can open a Cursor.
declare
v_sql varchar2(100) := 'select 1,2,3,4,5 from dual';
cur_ref SYS_REFCURSOR;
function get_data(in_sql in varchar2) return SYS_REFCURSOR
as
cur_ret SYS_REFCURSOR;
begin
OPEN cur_ret FOR in_sql;
return cur_ret;
end;
begin
:cur_ref := get_data(v_sql);
end ;
if your select statement is longer than 32K than you maybe should use a clob instead of varchar2 for your Parameter type. But you have to try that

Does Oracle close the SYS_REFCURSOR returned from a function which is used in a SELECT statement?

I need to store some queries in the DB (inside functions in packages) and then call the functions from SQL Developer (from ORDS), so I found that you can return the queries in SYS_REFCURSORs from the stored functions like this:
CREATE OR REPLACE FUNCTION test RETURN SYS_REFCURSOR AS
vRC SYS_REFCURSOR;
BEGIN
OPEN vRC FOR SELECT *
FROM employees
WHERE empid = 34650;
RETURN vRC;
END;
And later on, simply retrieve the data in SQL Developer like this:
SELECT test
FROM dual;
So my question is...
Is this correct? I know that everytime we OPEN a cursor we need to explicitaly CLOSE it and every example of this that I have foud close the refcursor in PL/SQL and I need to get the data from a SELECT statement (in order to parse it to JSON in SQL DEVELOPER).
Also I have found that in SQLPlus the "print" statement closes the cursor once it fetched all the data, does the SELECT statement im my example do this as well?
You can get the cursor and print its contents like this:
VARIABLE cur REFCURSOR;
BEGIN
:cur := test();
END;
/
PRINT cur;
The PRINT and the SELECT statements will both read the cursor and when they have read all the rows they will implicitly close the cursor. You cannot read the data from a cursor twice (however, you can call the function multiple times to get multiple cursors all containing the same information).

Looping over cursor returned from procedure with many tables

I'm working with a stored procedure that I didn't write, which is long and contains numerous columns and joins. The procedure returns a cursor, which the application server (.NET, incidentally) picks up and iterates through.
I'm trying to intercept the cursor using SQLPlus and PL/SQL but I'm having a hard time trying to figure out how to set up the script. Here's what I have so far:
DECLARE
cur sys_refcursor;
BEGIN
adv_schema.report_proc('CAL','01-JAN-2011','01-JAN-2012','Y',cur);
OPEN cur FOR --??????
LOOP
FETCH cur INTO column1, column2;
EXIT WHEN cur%NOTFOUND;
DBMS_OUTPUT.Put_Line ('First Name: '||column1||' Last Name: '||column2);
END LOOP;
END;
/
What do I put in the OPEN statement? All the examples I've seen of how to do this are overly simplified examples where some table 't' is created right there in the PL/SQL block, then a cursor is opened with a query to that table, to loop over. What about when a procedure returns a cursor to a complex query with multiple tables?
Assuming that the report_proc procedure is returning the cursor (i.e. the fourth parameter is defined as an OUT SYS_REFCURSOR), there is no need to OPEN the cursor in your code. The procedure is already opening it. You just need to fetch from it.
As #Justin has already mentioned, there is no need to open the cursor returned by the report_proc procedure, you only need to fetch from that cursor. Ref cursors are weak cursors(basically cursors that return no type) and in order to fetch from a weakly typed cursor, you need to know what to fetch. When you know what type a cursor returns you can declare a local structure to fetch into, like so:
DECLARE
-- example of record
-- in your case you have to know exactly
-- how many column and of which datatype your ref cursor returns
type T_Record is record(
column_1 number,
column_2 number
);
l_record T_Record;
cur sys_refcursor;
BEGIN
adv_schema.report_proc('CAL','01-JAN-2011','01-JAN-2012','Y',cur);
LOOP
FETCH cur
INTO l_record;
EXIT WHEN cur%NOTFOUND;
DBMS_OUTPUT.Put_Line ('First Name: '||l_record.column1
||' Last Name: '||l_record.column2);
END LOOP;
END;
Also, if you simply need to print the content of a ref cursor, you can do it in SQL*Plus as follows:
SQL> variable cur refcursor;
SQL> exec adv_schema.report_proc('CAL','01-JAN-2011','01-JAN-2012','Y', :cur);
And then use print command to print the refcursor cur:
SQL> print cur;
Do I need to fetch every column that the cursor returns, or can I fetch a subset, say the first three.
No, you fetch everything, you cannot be selective in what column to fetch. It's not impossible however, but it will involve using dbms_sql package, specifically dbms_sql.describe_columns procedure to get information about columns for a cursor.
Just for consideration, If you know that a specific column is definitely present in a cursor, you could use xmlsequence() function to fetch a specific column specifying its name in the extract() function:
SQL> declare
2 type T_List is table of varchar2(123);
3 l_names T_List;
4 l_ref_cur sys_refcursor;
5
6 begin
7 open l_ref_cur
8 for select first_name, last_name
9 from employees
10 where rownum <= 5;
11
12 SELECT t.extract('ROW/FIRST_NAME/text()').getstringval()
13 bulk collect into l_names
14 FROM table(xmlsequence(l_ref_cur)) t;
15
16 for indx in l_names.first..l_names.last
17 loop
18 dbms_output.put_line(l_names(indx));
19 end loop;
20 end;
21 /
Result:
Ellen
Sundar
Mozhe
David
Hermann
PL/SQL procedure successfully completed

How to repeat Select statements in a loop in Oracle?

I am reading a lot of stuff about repeating Select statements within a loop but I am having some difficulties as I have not found something clear till now. I want to execute some queries (Select queries) several times, like in a FOR loop. Can anyone help with some example please?
The basic structure of what you are asking can be seen below.
Please provide more information for a more specific code sample.
DECLARE
l_output NUMBER;
BEGIN
FOR i IN 1..10 LOOP
SELECT 1
INTO l_output
FROM dual;
DBMS_OUTPUT.PUT_LINE('Result: ' || l_output);
END LOOP;
END;
PS: If you need to enable output in SQL*Plus, you may need to run the command
SET SERVEROUTPUT ON
UPDATE
To insert your results in another table:
DECLARE
-- Store the SELECT query in a cursor
CURSOR l_cur IS SELECT SYSDATE DT FROM DUAL;
--Create a variable that will hold each result from the cursor
l_cur_rec l_cur%ROWTYPE;
BEGIN
-- Open the Cursor so that we may retrieve results
OPEN l_cur;
LOOP
-- Get a result from the SELECT query and store it in the variable
FETCH l_cur INTO l_cur_rec;
-- EXIT the loop if there are no more results
EXIT WHEN l_cur%NOTFOUND;
-- INSERT INTO another table that has the same structure as your results
INSERT INTO a_table VALUES l_cur_rec;
END LOOP;
-- Close the cursor to release the memory
CLOSE l_cur;
END;
To create a View of your results, see the example below:
CREATE VIEW scott.my_view AS
SELECT * FROM scott.emp;
To view your results using the view:
SELECT * FROM scott.my_view;

Check Values in sys_refcursor

I have the following code in a function
CREATE OR REPLACE FUNCTION my_func (
v_dt events.raised_date%TYPE
)
RETURN SYS_REFCURSOR
IS
p_events SYS_REFCURSOR;
OPEN p_events FOR
SELECT event_id
FROM events
WHERE raised_date = v_dt;
RETURN p_events;
END;
I would like to check whether 100 exists in p_events cursor or not. How can I do this inside my function.
Any help is highly appreciable.
A ref cursor is just a pointer to query. There is nothing "in" it. So the only way to find out whether the result set identified by the ref cursor contains a specific record - or indeed any records - is to fetch the cursor and read through the records.
Bear in mind that a ref cursor is a one-shot thang. We cannot fetch the same cursor more than once. We have to close and re-open it. But that means we run the risk of the second fetched result set differing from the first (unless we change the transaction's isolation level).
So the upshot is, just code the consuming procedure to fetch and use the ref cursor, and make sure it handles both the presence and absence of interesting records.
It is not good idea to check it inside of the function. You are missing why the cursor is returned. Instead do it outside of the function.
DECLARE
l_rc SYS_REFCURSOR := my_func();
TYPE events_ntt IS TABLE OF NUMBER;
l_events events_ntt;
l_lookup events_ntt := events_ntt(100);
l_diff events_ntt;
BEGIN
FETCH l_rc BULK COLLECT INTO l_events;
l_diff := l_events MULTISET INTERSECT DISTINCT l_lookup;
IF l_diff.COUNT > 0 THEN
DBMS_OUTPUT.PUT_LINE('100 EXISTS');
ELSE
DBMS_OUTPUT.PUT_LINE('100 DOES NOT EXIST');
END IF;
END;
Using Cursor Variables (REF CURSORs)
Like a cursor, a cursor variable points to the current row in the
result set of a multi-row query. A cursor variable is more flexible
because it is not tied to a specific query. You can open a cursor
variable for any query that returns the right set of columns.
You pass a cursor variable as a parameter to local and stored
subprograms. Opening the cursor variable in one subprogram, and
processing it in a different subprogram, helps to centralize data
retrieval. This technique is also useful for multi-language
applications, where a PL/SQL subprogram might return a result set to a
subprogram written in a different language, such as Java or Visual
Basic.
What Are Cursor Variables (REF CURSORs)?
Cursor variables are like pointers to result sets. You use them when
you want to perform a query in one subprogram, and process the results
in a different subprogram (possibly one written in a different
language). A cursor variable has datatype REF CURSOR, and you might
see them referred to informally as REF CURSORs.
Unlike an explicit cursor, which always refers to the same query work
area, a cursor variable can refer to different work areas. You cannot
use a cursor variable where a cursor is expected, or vice versa.
Source: http://docs.oracle.com/cd/B19306_01/appdev.102/b14261/sqloperations.htm#i7106
(Oracle Database PL/SQL User's Guide and Reference)
It can be done like this
declare
evt EVENTS%ROWTYPE;
found_100 boolean := false;
begin
loop
fetch p_events into evt;
exit when p_events%NOTFOUND;
if evt.event_id = 100 then
found_100 := true;
exit;
end if;
end loop;
end;
but however it's very inefficient because you're possibly fetching millions of records where you actually only need 1 fetch.