sqloracle-databaseplsql

Count rows in a sys_refcursor of an unknown type


I have a PL/SQL function similar to this (pseudo code) :

  function foobar() return sys_refcursor as
    select_cursor sys_refcursor;
  begin
    open select_cursor for
      select
        a, b, c, d, e, f, g, h, i, j
      from
        table_a
        left join table_b on ...
        left join table_c on ...
        left join table_d on...
        left join table_e on ...
        ...
      where
       ...
      ;

    return select_cursor;
  end;

As you can see, the type of the cursor is not simply table_a%rowtype. It's something too complex to just declare (EDIT: that's not entirely true, see the answer by MT0)

How do I count the number of rows returned by the function?

Constraints:

I have found lots of answer here, but never for counting on an unknown type.

For example:

  function count_nodes(
    p_cursor in sys_refcursor
  )
  return number is
    v_node_row   ???; -- unknown type
    v_count      number := 0;
  begin

      loop
        fetch p_cursor into v_node_row;
        exit when p_cursor%NOTFOUND;
        v_count := v_count + 1;
      end loop;

    close p_cursor;

    return v_count;
  end;

I've asked the AI, but it hallucinated that I can just FETCH INTO a dummy variable of type NUMBER if the first column in the function's SELECT is a NUMBER (which it is!). But then when I run the query, I get:

ORA-06504: PL/SQL: Return types of Result Set variables or query do not match


Solution

  • If the goal is just to count rows then Cast a column from the main table as VARCHAR2 to have always the same datatype. If you need the rest of the columns for some other reasons cast them all (commented out in the code), but for the counting you need just one.
    Here is the simulation of the data with your foobar function code:

    create or replace function foobar return sys_refcursor as
        select_cursor sys_refcursor;
      begin
        open select_cursor for
        SELECT a 
        FROM (  select  CAST(a as VARCHAR2(4000)) as a, 
                        b,    -- CAST(b as VARCHAR2(4000)) as b, 
                        c     -- CAST(c as VARCHAR2(4000)) as c
               from ( Select 1 as id, 456 as a From Dual ) table_a
               left join ( Select 1 as id, 'some text' as b From Dual ) table_b on(table_b.id = table_a.id)
               left join ( Select 1 as id, Trunc(SYSDATE) as c From Dual ) table_c on(table_c.id = table_b.id)
               where a > 400
             );
        return select_cursor;
      end;
    

    ... now create another function that should do the count ...

    CREATE OR REPLACE FUNCTION COUNT_REF_C RETURN NUMBER AS 
        c sys_refcursor := foobar;
        v_a   Varchar2(4000);
        -- v_b   Varchar2(4000);
        -- v_c   Varchar2(4000);
        i     NUMBER(6) := 0;
      Begin
        LOOP
          FETCH c INTO v_a;  --, v_b, v_c;
          EXIT WHEN c%NOTFOUND;
          i := i + 1;
        END LOOP; 
        RETURN i;
      End;
    

    ... Select the function's returned value ...

    Select COUNT_REF_C From Dual;
    
    --  R e s u l t :
    --  COUNT_REF_C
    --  -----------
    --            1