PLSQL: Procedure outputting multiple cursors - oracle

I'd like to return multiple cursor in one procedure, one based on other.
My currently code is:
TYPE REFCURSOR IS REF CURSOR;
PROCEDURE GETCARS(oCARS OUT REFCURSOR)
BEGIN
OPEN oCARS FOR SELECT * FROM CARS;
END GETCARS;
I'm not sure if that's posible but I want to make something like:
PROCEDURE GETCARS(oCARS OUT REFCURSOR, oREPAIRS OUT REFCURSOR)
BEGIN
OPEN oCARS FOR SELECT * FROM CARS;
..??..
END GETCARS;
which would return as a second parameter all repairs connected with currently fetched oCARS row.
(Table repairs has a FK for an id_car from cars)
Now I do that on C# side, when I fetch one row from oCARS cursor I call second procedure which gives me list of repairs, but maybe it's somehow possible to do that in one procedure (which would give me performance gain? - I don't want to use join, cause it returns multiplied cars for each repair)

The simple answer is that you can't do what you are attempting.
Cursors are basically just pointers to the start of the result set that contains the query results. Until you fetch a row, there is no way to know what that row will contain. Because your application, rather than the PL/SQL code, is doing the fetching, the PL/SQL portion has no knowledge of the values being returned.
To do what you're attempting, the database would have to detect the fetch from the first query, create a new result set using the second query, then place the new result set at the address that the procedure originally returned for the second cursor. Databases just aren't designed to handle this kind of operation.

How about
PROCEDURE GETCARS(oCARS OUT SYS_REFCURSOR, oREPAIRS OUT SYS_REFCURSOR, oCHARGES OUT SYS_REFCURSOR)
BEGIN
OPEN oCARS FOR SELECT * FROM CARS;
OPEN oREPAIRS FOR SELECT * FROM REPAIRS;
OPEN oCHARGES FOR SELECT * FROM CHARGES;
END GETCARS;
Share and enjoy.

Related

Partially consuming a cursor in multiple pl/sql calls without defining it in package spec

I have a large source data set (a few million rows) that requires complex processing, resulting in much larger amount of data, which should be then offloaded and stored as files. The storage requires dividing up resulting data based on certain parameters, namely N source rows that meet certain criteria.
Since it's possible to compute the said parameters within PL/SQL, it was decided that the most efficient way would be to create a package, specify a spec-level cursor for source rows in it, then write a procedure that would partially consume the opened cursor until the criteria is meet and fill temporary tables with resulting data, which would then be offloaded, and the procedure would be called again, repeating until there's no more source rows. PL/SQL basically looks like this:
create or replace PACKAGE BODY generator as
cursor glob_cur_body(cNo number) is
select *
from source_table
where no = cNo
order by conditions;
procedure close_cur is
begin
if glob_cur_body%isopen then
close glob_cur_body;
end if;
end close_cur;
procedure open_cur(pNo number) is
begin
close_cur;
open glob_cur_body(pNo);
end open_cur;
function consume_cur return varchar2 is
v source_table%rowtype;
part_id varchar2(100);
begin
fetch glob_cur_body into v;
if glob_cur_body%notfound then
return null;
end if;
--Clear temporary tables
--Do the processing until criteria is meet of there's no more rows
--Fill the temporary tables and part_id
return part_id;
end consume_cur;
end generator;
And the consumer is doing the following (in pseudocode)
generator.open_cur;
part_id = generator.consume;
while ( part_id != null )
{
//offload data from temp tables
part_id = generator.consume;
}
generator.close_cur;
It's working fine, but unfortunately there's one problem: a spec-level cursor makes the package stateful, meaning that its recompilation results in ORA-04068 for sessions that already accessed it before. It makes maintenance cumbersome, because there's a lot more to the package besides said functions, and it's actively used for unrelated purposes.
So, I want to get rid of the spec-level cursor, but I'm not sure if that's possible. Some ideas I've already discarded:
Re-opening the cursor and skipping N rows: terrible performance, unreliable because affected by any changes to data made between opens
Fetching the source cursor into plsql table: size too large.
Filling up the entire unload tables at once, splitting them later: size too large, subpar performance.
Opening the cursor as refcursor and storing refcursor variable in a dedicated package: impossible, as pl/sql doesn't allow sys_refcursor variables at spec levels
Having open_cur procedures return refcursor, storing it in the offloader, and then somehow passing it to consume_cur: looked viable, but the offloader is in Java, and JDBC doesn't allow binding of SYS_REFCURSOR parameters.
Changing consume_cur to pipelined function: could have worked, but oracle buffers pipelined rows, meaning it would execute multiple times when fetching data from it row-by-row. Also counterintuitive.
Only other idea I've had so far is to make a dedicated package storing said cursor, having open and close procedures and get_cursor returning refcursor; then call get_cursor from generator.consume_cur. That would make the dedicated package (which is unlikely to change) stateful and main package stateless. However, it seems like a half-baked patch rather than a problem solution. Is there a more decent way of achieving what I need? Perhaps changing the logic completely without affecting performance and storage limits too much.
I have a problem to understand your question. But I can provide clarification for your ideas.
Opening the cursor as refcursor and storing refcursor variable in a
dedicated package: impossible, as pl/sql doesn't allow sys_refcursor
variables at spec levels
The workaround with dbms_sql.
create table test_rows as (select level rr from dual connect by level <= 100);
create or replace package cursor_ctx is
ctx_number integer;
end;
declare
p_cursor sys_refcursor;
begin
open p_cursor for 'select rr from test_rows';
cursor_ctx.ctx_number := DBMS_SQL.TO_CURSOR_NUMBER(p_cursor);
end;
This part consuming is data from the cursor.
declare
p_cursor sys_refcursor;
type l_number is table of number;
v_numbers l_number;
begin
if DBMS_SQL.IS_OPEN(cursor_ctx.ctx_number) then
p_cursor := DBMS_SQL.TO_REFCURSOR( cursor_ctx.ctx_number);
fetch p_cursor bulk collect into v_numbers limit 10;
if v_numbers.count < 10 then
dbms_output.put_line('No more data, close cursor');
close p_cursor;
cursor_ctx.ctx_number := null;
else
cursor_ctx.ctx_number := DBMS_SQL.TO_CURSOR_NUMBER(p_cursor);
end if;
for i in nvl(v_numbers.first,1) .. nvl(v_numbers.last,-1) loop
dbms_output.put_line(v_numbers(i));
end loop;
else
dbms_output.put_line('Null or cursor close ');
end if;
end;
Pipelined function has future to split input cursor into chunk. Parallel Enabled Pipelined Table Functions
JDBC allows using sys_refcursor as an output parameter. sys_refcursor = ResultSet.

Construct and debug a PL/SQL simple or complex select view statement in a procedure

How do I perform a select on a rather simple view in oracle pl/sql using a stored procedure.
Lets say the view looks like this:
FirstName LastName
-------- -------
Bob Jones
James Kay
etc...
To me its should be so simple:
Procedure SuperSimple()
begin
select FirstName, LastName from SuperSimple
end
However I've been told that this will not work.
So I tried to use a PL/SQL cursor. Still scratching my head trying to figure out why I am using cursors. But it appears to be necessary in 11g.
Procedure AlphaPrime(Results OUT Ref CURSOR) IS
begin
OPEN Results for
select FirstName, LastName from SuperSimple;
end;
Now I was hoping this would work but I'm doing something like this with select statements and it appears to be not working.
Do I also need to add a fetch and another open and a close command to make this thing work? What is the idea behind all this? I've noticed that trying to find info on how to add a very simple select statemetn to a procedure appears to be missing from most documentation that I've read. Is there a reason for this like its too simple to add a select statement to a procedure as it would be better to add it to a view. Something along those lines.
The problem I'm having is I want to start out really simple and tac on a little bit more complexity to the sproc over time... where time ~ 1 to 2 hours. Can someone point me to some docs in Oracle PL/SQL that shows how to add a simple table or view. Also If the permissions for a specific view or table is not allowed does it just fail for that user or does it give an empty result set.
It is not clear from your question what are you intending to do with the query result inside your procedure. So here I make some examples with dbms_output which prints to screen out some message and data from your query. Probably you will replace it with your logic.
Let's have some view (actually it doesn't matter here whether you are querying view or table, but I would stick to your question)
create table some_simple_table(firstname varchar2(30), lastname varchar2(30));
/
create or replace view supersimple_view as select firstname, lastname, 'whatever1' whatever from some_simple_table;
/
The following code does select into variable, this will work only if query returns exactly one row.
create or replace procedure supersimple1 is
vfirstname supersimple_view.firstname%type;
vwhatever supersimple_view.whatever%type;
vsupersimple supersimple_view%rowtype;
begin
select firstname, whatever into vfirstname, vwhatever from supersimple_view;
dbms_output.put_line('I''m doing some logic with this'|| vwhatever );
select * into vsupersimple from supersimple_view;
dbms_output.put_line('I''m doing some logic with this'|| vsupersimple.firstname);
end;
/
Perhaps you can implement implicit cursor loop through results and do some logic.
create or replace procedure supersimple2 is
begin
for rec in (select * from supersimple_view)
loop
dbms_output.put_line('I''m doing some logic with this record '|| rec.firstname);
end loop;
end;
/
Another option is cursor (particularly in case when you will reuse the same select) loop through results and do some logic.
create or replace procedure supersimple3 is
cursor cur is (select * from supersimple_view);
vsupersimple cur%rowtype;
begin
open cur ;
loop
FETCH cur INTO vsupersimple;
EXIT WHEN cur%NOTFOUND;
dbms_output.put_line('I''m doing some logic with this record '|| vsupersimple.firstname);
end loop;
close cur;
end;
/
You can fetch result of your query to collection
create or replace procedure supersimple4 is
type supersimple_colt is table of supersimple_view%rowtype index by pls_integer;
vsupersimple_col supersimple_colt;
begin
select * bulk collect into vsupersimple_col from supersimple_view ;
for i in 1 .. vsupersimple_col.count
loop
dbms_output.put_line('I''m doing some logic with this record '|| vsupersimple_col(i).firstname);
end loop;
end;
/
Instead of PL/SQL type declared in supersimple4 you can create standalone database SQL types and used them to fetch results into. This aproach gives you various features like: possibility to query collection in select statement in table like fashion, converting it to xml by xmltype, etc.
I think I found the answer. For each column that is selected on, it needs a view or table column type, which is sort of like the list of parameters used for the final output. That way when you declare on it you can better know what you are getting, which sorta makes sense.
So if you have two tables or views which were used to generate the output columns, you would need both of those tables or views in your descriptive OUT variables to describe better what you are outputting in the final output result.
See this link.
I'm taking an educated guess with this next part as I'm just beginning to understand it:
This query should work. But if its not it may be due to insuffiecient priviledges. Try a table that you know you have access and select it in a procedure in debug mode. Then try a view.
Procedure AlphaPrime(Results OUT Ref CURSOR) IS
begin
OPEN Results for
select FirstName, LastName from SuperSimple;
end;
Also there is a possibility with Debug mode and your assigned user roles that you may have insufficient priviledges to debug all the objects in the view and they may not be accessible. Sometimes you can just hit the "Ignore" button in Toad to skip over debugging inside a stored procedure. Also you may have priveledges to view the results the object just not view its structure which may also give you insufficient priviledges errors. Again just ignore them to skip over those types of issues and see the results while in debug mode. If you don't debug this, then you should not see any errors and just get the results.

How to get refcursor result/output to show as text?

I'm trying to call a stored procedure in Oracle and show the results of the call, the problem is that it crashes on the line FETCH v_cur into v_a; with error: ORA-06504: PL/SQL: Return types of Result Set variables or query do not match.
I guess the output of the query does not match v_a VARCHAR2(100), but I don't know what to put there instead. The stored procedure that's being called does a join of several tables and selects more than 20+ different columns belonging to different tables. So what I would want is to just view the output of the query without having to refer to each result column separately. How I would go and do this ?
I'm using SQL Navigator (not that important I guess).
DECLARE
v_cur SYS_REFCURSOR;
v_a VARCHAR2(100);
BEGIN
pkg_get_results.get_rows(v_cur,to_date('2012/04/12', 'yyyy/mm/dd'),to_date('2012/04/12', 'yyyy/mm/dd'), '','','','');
LOOP
FETCH v_cur into v_a; -- what to put here ?
EXIT WHEN v_cur%NOTFOUND;
dbms_output.put_line(v_a );
END LOOP;
CLOSE v_cur;
END;
SQL Navigator does have the ability to do this for you. How to do it exactly depends on your version of Navigator, and it's conceivable (though I don't know) some versions may not have it.
Instructions can be found in this thread: http://sqlnavigator.inside.quest.com/thread.jspa?threadID=2466
Incidentally, Toad also has this ability.

Write Oracle Procedure To Accept List of Items used in Select

There have been a couple of hints that seem to have gotten me close here, but with some unique issues, I'm hoping this question is distinguishing enough to merit its own posting.
For starters here's what I have. I have an Oracle procedure that returns a standard REF CURSOR, and this REF CURSOR is passed back to my application. The REF CURSOR is a list of lookup IDs.
I then want to take this list and bring it to another data store and use it in a select statement. It will absolutely be possible to accomplish this by looping through the REF CURSOR, but I'm hoping to avoid that. I would much rather be able to write a SELECT...WHERE lookup_id IN result_ref_cursor OR SELECT...WHERE EXISTS...
First is this possible or should I just try a less than elegant solution? If it is possible, any hints as to where I should get started looking?
I'm relatively new to Oracle, but fairly experienced in RDBMs in general, so feel free to just through some links at me and I can study up. Much appreciated
Why kurosch didn't put his response as an "answer" I'll have no idea.
So, what you do is define a SQL type which describes one row of the output of the ref cursor, and also a SQL type which is a table of the previous. Then, you'll create a pipelined function which returns the rows returned by the ref cursor. This function can then be used in a standard SQL. I'm borrowing from Ask Tom on this one.
create or replace type myLookupId as object ( id int)
/
create or replace type myLookupIdTable as table of myLookupId
/
create or replace function f return myLookupIdTable PIPELINED is
l_data myLookupId;
l_id number;
p_cursor SYS_REFCURSOR;
begin
p_cursor := function_returning_ref_cursor();
loop
fetch p_cursor into l_id;
exit when p_cursor%notfound;
l_data := myLookupId( l_id );
pipe row (l_data);
end loop;
return;
end;
/
And now a sample query...
SELECT *
FROM SOME_TABLE
WHERE lookup_id in (SELECT ID FROM table(f));
Sorry if the code isn't exactly right, I don't have the DB to test right now.
There are several directions you could go with this, but I did a search on the specific solution you want and it seems like no-one has done it often enough to show up there. What you can do is search the oracle metalink - that is usually really good at finding obscure answers. (Though you do need a service agreement - just found out that mine expired :( )
Other possible solutions:
Create a link between the data stores so that you can do the select in the plsql directly
Create a function in Java that loops through it for you to create the string for the query. This will look a little more pretty at least.
Otherwise, REF CURSOR's need to go back and forth - I don't know how you can pipe the results of the REF CURSOR in one connection to the query in another without looping through it.

Getting results in a result set from dynamic SQL in Oracle

This question is similar to a couple others I have found on StackOverflow, but the differences are signficant enough to me to warrant a new question, so here it is:
I want to obtain a result set from dynamic SQL in Oracle and then display it as a result set in a SqlDeveloper-like tool, just as if I had executed the dynamic SQL statement directly. This is straightforward in SQL Server, so to be concrete, here is an example from SQL Server that returns a result set in SQL Server Management Studio or Query Explorer:
EXEC sp_executesql N'select * from countries'
Or more properly:
DECLARE #stmt nvarchar(100)
SET #stmt = N'select * from countries'
EXEC sp_executesql #stmt
The question "How to return a resultset / cursor from a Oracle PL/SQL anonymous block that executes Dynamic SQL?" addresses the first half of the problem--executing dynamic SQL into a cursor. The question "How to make Oracle procedure return result sets" provides a similar answer. Web search has revealed many variations of the same theme, all addressing just the first half of my question. I found this post explaining how to do it in SqlDeveloper, but that uses a bit of functionality of SqlDeveloper. I am actually using a custom query tool so I need the solution to be self-contained in the SQL code. This custom query tool similarly does not have the capability to show output of print (dbms_output.put_line) statements; it only displays result sets. Here is yet one more possible avenue using 'execute immediate...bulk collect', but this example again renders the results with a loop of dbms_output.put_line statements. This link attempts to address the topic but the question never quite got answered there either.
Assuming this is possible, I will add one more condition: I would like to do this without having to define a function or procedure (due to limited DB permissions). That is, I would like to execute a self-contained PL/SQL block containing dynamic SQL and return a result set in SqlDeveloper or a similar tool.
So to summarize:
I want to execute an arbitrary SQL statement (hence dynamic SQL).
The platform is Oracle.
The solution must be a PL/SQL block with no procedures or functions.
The output must be generated as a canonical result set; no print statements.
The output must render as a result set in SqlDeveloper without using any SqlDeveloper special functionality.
Any suggestions?
The closest thing I could think of is to create a dynamic view for which permission is required. This will certainly involve using a PL/SQL block and a SQL query and no procedure/function. But, any dynamic query can be converted and viewed from the Result Grid as it's going to be run as a select query.
DEFINE view_name = 'my_results_view';
SET FEEDBACK OFF
SET ECHO OFF
DECLARE
l_view_name VARCHAR2(40) := '&view_name';
l_query VARCHAR2(4000) := 'SELECT 1+level as id,
''TEXT''||level as text FROM DUAL ';
l_where_clause VARCHAR2(4000):=
' WHERE TRUNC(1.0) = 1 CONNECT BY LEVEL < 10';
BEGIN
EXECUTE IMMEDIATE 'CREATE OR REPLACE VIEW '
|| l_view_name
|| ' AS '
|| l_query
|| l_where_clause;
END;
/
select * from &view_name;
You seem to be asking for a chunk of PL/SQL code that will take an arbitrary query returning result set of undetermined structure and 'forward/restructure' that result set in some way such that is can easily be rendered by some "custom GUI tool".
If so, look into the DBMS_SQL for dynamic SQL. It has a DESCRIBE_COLUMNS procedure which returns the columns from a dynamic SELECT statement. The steps you would need are,
Parse the statement
Describe the result set (column names and data types)
Fetch each row, and for each column, call the datatype dependent function to return that value into a local variable
Place those local variables into a defined structure to return to the calling environment (eg consistent column names [such as col_1, col_2] probably all of VARCHAR2)
As an alternative, you could try building the query into an XMLFOREST statement, and parse the results out of the XML.
Added :
Unlike SQL Server, an Oracle PL/SQL call will not 'naturally' return a single result set. It can open up one or more ref cursors and pass them back to the client. It then becomes the client's responsibility to fetch records and columns from those ref cursors. If your client doesn't/can't deal with that, then you cannot use a PL/SQL call.
A stored function can return a pre-defined collection type, which can allow you to do something like "select * from table(func_name('select * from countries'))". However the function cannot do DML (update/delete/insert/merge) because it blows away any concept of consistency for that query. Plus the structure being returned is fixed so that
select * from table(func_name('select * from countries'))
must return the same set of columns (column names and data types) as
select * from table(func_name('select * from persons'))
It is possible, using DBMS_SQL or XMLFOREST, for such a function to take a dynamic query and restructure it into a pre-defined set of columns (col_1, col_2, etc) so that it can be returned in a consistent manner. But I can't see what the point of it would be.
Try try these.
DECLARE
TYPE EmpCurTyp IS REF CURSOR;
v_emp_cursor EmpCurTyp;
emp_record employees%ROWTYPE;
v_stmt_str VARCHAR2(200);
v_e_job employees.job%TYPE;
BEGIN
-- Dynamic SQL statement with placeholder:
v_stmt_str := 'SELECT * FROM employees WHERE job_id = :j';
-- Open cursor & specify bind argument in USING clause:
OPEN v_emp_cursor FOR v_stmt_str USING 'MANAGER';
-- Fetch rows from result set one at a time:
LOOP
FETCH v_emp_cursor INTO emp_record;
EXIT WHEN v_emp_cursor%NOTFOUND;
END LOOP;
-- Close cursor:
CLOSE v_emp_cursor;
END;
declare
v_rc sys_refcursor;
begin
v_rc := get_dept_emps(10); -- This returns an open cursor
dbms_output.put_line('Rows: '||v_rc%ROWCOUNT);
close v_rc;
end;
Find more examples here. http://forums.oracle.com/forums/thread.jspa?threadID=886365&tstart=0
In TOAD when executing the script below you will be prompted for the type of v_result. From the pick list of types select cursor, the results are then displayed in Toad's data grid (the excel spreadsheet like result). That said, when working with cursors as results you should always write two programs (the client and the server). In this case 'TOAD' will be the client.
DECLARE
v_result sys_refcursor;
v_dynamic_sql VARCHAR2 (4000);
BEGIN
v_dynamic_sql := 'SELECT * FROM user_objects where ' || ' 1 = 1';
OPEN :v_result FOR (v_dynamic_sql);
END;
There may be a similar mechanism in Oracle's SQL Developer to prompt for the binding as well.

Resources