Home > OS >  Oracle Cursor - Return result and rowcount
Oracle Cursor - Return result and rowcount

Time:06-04

I'm trying to create a procedure in Oracle that would return the result of the query and at the same time count the number of returned rows. Is it possible to do that in one procedure? This one returns the result of the query (this is an example query, our production query is more complicated):

CREATE OR REPLACE PROCEDURE GETCUR(PARAM1 VARCHAR2)
AS
cur SYS_REFCURSOR;
cSql NUMBER;
cnt INTEGER;

BEGIN
   OPEN CUR FOR
   SELECT T1.F3, T2.F3 FROM T1 JOIN T2 ON T1.F1 = T2.F2 WHERE T2.F9 = PARAM1;
   
   DBMS_SQL.RETURN_RESULT(CUR);
END;

And this one can count the number of returned rows. Here I print it using PUT_LINE but want to be able to assign it to some variable and insert that into other table - something like a logging mechanism.

CREATE OR REPLACE PROCEDURE GETCUR(PARAM1 VARCHAR2)
AS
cur SYS_REFCURSOR;
cSql NUMBER;
cnt INTEGER;
       
BEGIN
  OPEN CUR FOR
    SELECT T1.F3, T2.F3 FROM T1 JOIN T2 ON T1.F1 = T2.F2 WHERE T2.F9 = PARAM1;
           
--DBMS_SQL.RETURN_RESULT(CUR);

cSql := DBMS_SQL.TO_CURSOR_NUMBER(CUR);
cnt := 0;
LOOP
  EXIT WHEN DBMS_SQL.FETCH_ROWS(cSql) = 0;
  cnt := cnt   1;
END LOOP;
    
DBMS_SQL.CLOSE_CURSOR(cSql);
DBMS_OUTPUT.PUT_LINE(cnt||' rows returned');
    
END;

But I can't marry these two solutions into one procedure. Is it possible?

CodePudding user response:

If you want to pass back two outputs - a cursor and a count, there is no way to do that without running two queries. That's inefficient and possibly inaccurate for reasons Justin Cave pointed out.

One thing you might do is something like this:

CREATE OR REPLACE FUNCTION GETCUR(PARAM1 VARCHAR2) RETURN SYS_REFCURSOR
AS
cur SYS_REFCURSOR;

BEGIN
   OPEN CUR FOR
   SELECT T1.F3, 
          T2.F3,
          COUNT(*) OVER ( PARTITION BY NULL ) ROW_COUNT
   FROM T1 JOIN T2 ON T1.F1 = T2.F2
   WHERE T2.F9 = PARAM1;
   
   RETURN cur;
END;

Your consuming application will know the total row count after fetching the 1st record. And, thanks to statement-level read consistency in Oracle, it does not require you to set the isolation level to guarantee accurate results. It's also more efficient than running two separate queries.

CodePudding user response:

You could do something like

CREATE OR REPLACE PROCEDURE GETCUR(p_PARAM1 OUT VARCHAR2,
                                   p_rc     OUT sys_refcursor,
                                   p_cnt    OUT integer )
AS
BEGIN
  OPEN p_rc
   FOR SELECT T1.F3, T2.F3 
         FROM T1 JOIN T2 ON T1.F1 = T2.F2 
        WHERE T2.F9 = p_PARAM1;  

  select count(*)
    into p_cnt
    from (SELECT T1.F3, T2.F3 
           FROM T1 JOIN T2 ON T1.F1 = T2.F2 
          WHERE T2.F9 = p_PARAM1);
end;

However,

  1. This is going to be twice as expensive because you are running the query twice.
  2. If the underlying tables are changing, it is possible that the count you get in the second step won't match the count you got from fetching from the cursor you opened in the first step. You could potentially address that by using a serializable transaction isolation level but that is going to have a number of impacts on your application so it's not something that should be done lightly.
  • Related