How to find Ref Cursor execution time in procedure? - oracle

I'm using Ref Cursor as output parameter for PLSQL Procedure. I need to maintain the exact start and end time of proc in log table.
The dummy code below:
Procedure(P1 IN NUMBER, P_REF_CUR OUT SYS_REFCURSOR)
IS
V_TS TIMESTAMP;
BEGIN
V_TS := SYSTIMESTAMP;
<Business logic here to generate SELECT query for Ref Cursor...>;
OPEN P_REF_CUR FOR <SELECT QUERY>;
INSERT INTO LOG_TABLE(ID, STR_TIME,END_TIME,..) VALUES
(1,V_TS,SYSTIMESTAMP,...);
END;
The select query for Ref Cursor sometimes takes 2-3 mins to execute but in log table I see the difference between STR_TIME and END_TIME as only few seconds.
How can I capture the total time taken by procedure including the query execution time?

Once your procedure hands the ref cursor back to the calling process, it has no way of knowing what will happen to it. The caller may never even fetch all of the rows from the cursor. It’s for the caller to log what happens next.

You may try to split this procedure into two packaged procedures, and apply set timing on :
SQL> create or replace package myPkg is
procedure pr1(P1 IN NUMBER);
procedure pr2(P_REF_CUR OUT SYS_REFCURSOR);
end;
/
SQL> create or replace package body myPkg is
v_ts timestamp;
procedure pr1(P1 IN NUMBER) is
begin
v_ts := SYSTIMESTAMP;
<Business logic here to generate SELECT query for Ref Cursor...>;
end;
procedure pr2(P_REF_CUR OUT SYS_REFCURSOR) is
begin
open P_REF_CUR for <SELECT QUERY>;
insert into log_table(ID, STR_TIME,END_TIME,..) values(1,V_TS,SYSTIMESTAMP,...);
end;
end;
/
SQL> set timing on;
SQL> var v_p1 number:=107;
SQL> var v_rc refcursor;
SQL> exec myPkg.pr1( :v_p1 );
PL/SQL procedure successfully completed
Executed in 152,25 seconds
SQL> exec myPkg.pr2( :v_rc );
PL/SQL procedure successfully completed
Executed in 12,34 seconds
SQL> print v_rc;

You can't tell from inside the procedure. The OPEN FOR statement:
... associates a cursor variable with a query, allocates database resources to process the query, identifies the result set, and positions the cursor before the first row of the result set.
All you can time in your procedure is how long it takes to generate the query text and how long it takes to open the cursor. The procedure then ends, and the caller takes over the OUT ref cursor. You can't see anything about what happens to the cursor from here.
The caller than (presumably) fetches the data, which is taking the bulk of the time; but may also be doing other processing. You need the caller to log the time between it calling your procedure and when it closes the ref cursor when it's finished with it - but that will still include any additional processing it does, so you can't separate out how much is actually from the cursor query processing and fetching.
If that is close enough then you could potentially have a second procedure that closes the cursor and logs the time, if you don't want that the caller to have to worry about it. You could have the 'open' cursor record the start time in a session variable (making the package stateful) and have the 'close' procedure retrieve that and insert the logging record; or have the 'open' do the initial insert into the logging table with a null end time, and then have the 'close' update that record with the actual end time. But again, it's only approximate.
if you really want to do it all in that procedure then you would have to do all the query processing within it, which probably means bulk collecting the cursor into a collection and using that collection type as the OUT parameter, adjusting your caller to iterate over that instead of the cursor. That has more memory overhead too of course, so may not be practical.

Related

How to print sqlplus output immediate

I have very simple pl\sql code.
In this code I'm printing the index of the loop and wait 1 second before each print.
My problem is that I want this output to be used like a live log, when the dbms_output.put_line procedure is invoked and print - I want to see the output immediately.
In the current code - only after it finished (5 seconds..), it prints all the output in one shot...
set serveroutput on
set echo on
begin
for i in 1..5
loop
dba_maint.pkg_utils.sp_sleep(1);
dbms_output.put_line(i);
end loop;
end;
/
No way, you can't. It is displayed when PL/SQL procedure has finished.
If you want to create a live log,
create a table
a sequence
an autonomous transaction procedure which would
insert a row into that table
using a sequence (so that you'd know how to order rows)
possibly a timestamp (so that you'd know how long certain step took)
commit (which won't affect main transaction as - remember - procedure is an autonomous transaction one)
Then put calls to the logging procedure into your long-time-run PL/SQL procedure, run it, and let it work. In another session, query the log table to view progress.

Populate an oracle collection type using a SYS_REFCURSOR and close the cursor

In my stored procedure, I have a code snippet like this:
OPEN p_result FOR
SELECT *
FROM TABLE (CAST ( l_data AS Rpt_mapping_TableType));
COMMIT;
p_result is an IN OUT parameter of type SYS_REFCURSOR.Rpt_mapping_TableType is a user defined collection type.
So this cursor will just populate the Rpt_mapping_TableType and then the program that calls this proc will read the results from Rpt_mapping_TableType.
My question is what is the use of COMMIT in this snippet? The code author says it is a way of closing the cursor. Is it right? My other question is if I just want to populate the collection , do I even need to do OPEN p_result FOR. After all I am not reading anything from the cursor so :
SELECT * FROM TABLE (CAST ( l_data AS Rpt_mapping_TableType));
should suffice.
No?
A commit will not close a cursor. If it did, then your code wouldn't work. (Although it could unlock rows from a FOR UPDATE, causing other problems.) Here's an example of a commit not closing a cursor:
SQL> variable test refcursor
SQL> begin
2 open :test for select 1 from dual;
3 end;
4 /
PL/SQL procedure successfully completed.
SQL> commit;
Commit complete.
SQL> print test;
1
----------
1
SQL>
If you just want to populate a collection, you're probably better off using something like SELECT ... BULK COLLECT INTO ... instead. (And possibly using a LIMIT.) The keyword CURSOR is frequently over-used. Unless you're passing data to another program, implicit cursors and bulk collects are usually much simpler and faster.

MyBatis and DBMS_OUTPUT

I am using Oracle/MyBatis and trying to debug a stored procedure with an enormous amount of parameters. Inside the stored procedure I get a ORA-01438: value larger than specified precision allowed for this column
So my initial approach would be to do like dbms_output.put_line in the stored procedure to try to see what the values are right before the offending statement. Without MyBatis, I would ordinarily open up a sqlplus script and type set serveroutput on and then run my stored procedure at some later point to see all the debug messages come out. With MyBatis, I cannot figure out how (if possible) to get these debug statements.
I have the ibatis and sql debuggers set for DEBUG and I use log4j to log everything for my Tomcat 6 application.
The DBMS_OUTPUT package has a few other procedures that you could use. DBMS_OUTPUT.ENABLE functions much like the SQL*Plus command set serveroutput on in that it allocates a buffer for DBMS_OUTPUT.PUT_LINE to write to. DBMS_OUTPUT.GET_LINE can be used to fetch the data written to that buffer by previous calls to DBMS_OUTPUT.PUT_LINE. So it should be possible to call the ENABLE function, call the procedure which writes a number of lines to the buffer, and then call GET_LINE (or GET_LINES) to fetch the data that was written to the DBMS_OUTPUT buffer and write that data to your logs.
It may be simpler, however, to redirect the logging to an Oracle database table rather than trying to use DBMS_OUTPUT. One common approach is to create your own package that has a switch to determine whether to write to DBMS_OUTPUT or whether to write to a table. Something like
CREATE OR REPLACE PACKAGE p
AS
procedure l( p_str IN VARCHAR2 );
END;
CREATE OR REPLACE PACKAGE BODY p
AS
g_destination INTEGER;
g_destination_table CONSTANT INTEGER := 1;
g_destination_dbms_out CONSTANT INTEGER := 2;
PROCEDURE l( p_str IN VARCHAR2 )
AS
BEGIN
IF( g_destination = g_destination_dbms_out )
THEN
dbms_output.put_line( p_str );
ELSE
INSERT INTO log_table ...
END IF;
END;
BEGIN
g_destination := <<determine which constant to set it to. This
may involve querying a `SETTINGS` table, looking
at the environment, or something else>>
END;
END;

PLSQL: Procedure outputting multiple cursors

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.

how do oracle stored procedures (w/ cursors) work?

I have a following oracle stored procedure
CREATE OR REPLACE
PROCEDURE getRejectedReasons
(
p_cursor IN OUT SYS_REFCURSOR)
AS
BEGIN
OPEN p_cursor FOR SELECT * FROM reasons_for_rejection;
END;
However, when I run this stored procedure in sql-developer then I dont see anything. I just see something like this:
Connecting to the database oracleLocal.
Process exited.
Disconnecting from the database oracleLocal.
I'm coming from MS sql server and am used to seeing actual results when running a stored procedure like this. Is this stored procedure not returning results because I am using a cursor??
The stored procedure is returning something it's just you aren't doing anything with the results.
You can do this simply by running the following script in SQLDeveloper:
VARIABLE csr REFCURSOR;
EXEC getRejectedReasons(:csr); -- the colon identifies the parameter as a variable
PRINT csr;
Another method is to fetch each row and do some sort of processing:
DECLARE
-- sys_refcursor is weakly typed
refcsr SYS_REFCURSOR;
-- define a record so we can reference the fields
rej_rec Reasons_for_Rejection%ROWTYPE;
BEGIN
getRejectedReasons(refcsr);
-- loop through the results
LOOP
-- gets one row at a time
FETCH refcsr INTO rej_rec;
-- if the fetch doesn't find any more rows exit the loop
EXIT WHEN refcsr%NOTFOUND;
-- Do something here.
-- For example : DBMS_OUTPUT.PUT_LINE(rej_rec.reason_desc);
END LOOP;
END;
You opened the cursor. You didn't select anything from it, update it, or advance it.
All open does, effectively, to select the matching rows into temporary memory, so you can advance the cursor row by row. Which you didn't do.
One of the differences between Oracle and SQL Server is that the latter returns result sets naturally. I'd use a function, by the way.
In Oracle, functions typically return a single element. Cursors came later.
There's some documentation online that will help you understand the use of refcursor bind variables. Here's one such for SQL*Plus:
http://download.oracle.com/docs/cd/B19306_01/server.102/b14357/ch5.htm#sthref1122
I think in SQL Developer you can do the same thing with autoprint on, although I haven't tested that.
Found a blog that also discusses something similar:
http://vadimtropashko.wordpress.com/cursors/
ETA: Ok. Ignore what I wrote. Listen to someone else. Apparently it's wrong, as I got down voted.
What tpdi said is correct. You have to do something with the cursor after you declare it.
Here's an example using two cursors in nested loops
PROCEDURE update_insert_tree (exid_in IN NUMBER, outvar_out OUT VARCHAR2)
IS
nxtid NUMBER;
phaseid NUMBER;
rowcounter1 NUMBER;
BEGIN
rowcounter1 := 0;
outvar_out := 0;
FOR acur IN (SELECT dept_exercise_id, phase
FROM ep_dept_exercise
WHERE exercise_id = exid_in)
LOOP
<<dept_loop>>
FOR thecur IN (SELECT document_name, thelevel, sortnum, type_flag,
ex_save_id
FROM ep_exercise_save
WHERE exercise_id = exid_in)
LOOP
phaseid := acur.phase;
IF phaseid = 0
THEN
phaseid := 10;
UPDATE ep_dept_exercise
SET phase = 10
WHERE dept_exercise_id = acur.dept_exercise_id;
END IF;
<<doc_loop>>

Resources