Search code examples
oracle-databaseplsqlsys-refcursor

Oracle: How to populate/insert row to a Ref Cursor?


Really need help regarding Ref Cursor. I have a Stored Procedure GET_PERSONROLES that have parameter type ref cursor. I just wanted to pupulate this ref cursor manually like inserting a row to the refcursor. Can I insert a row into a refcursor though a loop? Thank you in advance.

The procedure depends on this publicly declared type:

create or replace package types 
as 
    type cursorTypePersonRole is ref cursor; 
end;

Here is my pseudo-codeL

create or replace PROCEDURE GET_PERSONROLES
( 
  P_CURSOR IN OUT types.cursorTypePersonRole
) AS
  REFCUR SYS_REFCURSOR; 
  TYPE REFTABLETYPE IS RECORD (
    IS_MANAGER_LEVEL1 VARCHAR2(1),
    IS_MANAGER_LEVEL2 VARCHAR2(1)
  );
  TYPE TABLETYPE IS TABLE OF REFTABLETYPE;
  PERSONROLES_TABLETYPE TABLETYPE; 
BEGIN
  --calls another stored proc to populate REFCUR with data without problem
  MY_STOREDPROC('12345', REFCUR);
  LOOP
    FETCH REFCUR BULK COLLECT INTO PERSONROLES_TABLETYPE;
    EXIT WHEN PERSONROLES_TABLETYPE.COUNT = 0;
    FOR indx IN 1 .. PERSONROLES_TABLETYPE.COUNT 
    LOOP
       -- I'm able to query perfectly the values of IS_MANAGER_LEVEL1 and IS_MANAGER_LEVEL 2
       -- I'm aware that the below codes are wrong
       -- However this means I wanted to insert these values to a row of the cursor if possible
       -- Do some logic to know what data will be assigned in the row.
       if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL1 = 'Y' then
           P_CURSOR := <DO SOME LOGIC AND ASSIGN THE VALUE TO THE ROW>
       end if;
       if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL2 = 'Y' then
           P_CURSOR := <DO SOME LOGIC AND ASSIGN THE VALUE TO THE ROW>
       end if;
    END LOOP;
  END LOOP;     
  CLOSE REFCUR;
END GET_PERSONROLES;

Solution

  • A ref cursor is not a variable: it is a pointer to a result set which is consumed by the act of reading it. The result set itself is immutable.

    Immutability makes sense, because it reflects Oracle's emphasis on read consistency.

    The simplest way to produce the output you appear to want is to create a SQL Type

       open P_CURSOR for 
           select IS_MANAGER_LEVEL1,
                  IS_MANAGER_LEVEL2 
           from table ( PERSONROLES_TABLETYPE );
    

    This will work in 12c; in earlier versions to use the table() call like this you may need to declare REFTABLETYPE and TABLETYPE as SQL types( rather than in PL/SQL).


    "Ok edited it now"

    Alas your requirements are still not clear. You haven't given us the structure of the output ref cursor or shown what other processing you want to undertake.

    However, given the title of your question, let's have a guess. So:

    create or replace PROCEDURE GET_PERSONROLES ( P_CURSOR IN OUT types.cursorTypePersonRole) AS
      REFCUR SYS_REFCURSOR; 
      TYPE REFTABLETYPE IS RECORD (IS_MANAGER_LEVEL1 VARCHAR2(1),
        IS_MANAGER_LEVEL2 VARCHAR2(1));
      TYPE TABLETYPE IS TABLE OF REFTABLETYPE;
      PERSONROLES_TABLETYPE TABLETYPE; 
      personrole_rec PersonRole%rowtype;
      type personrole_nt is table of PersonRole%rowtype;
      personroles_recs personrole_nt := new personrole_nt() ; 
    BEGIN
        MY_STOREDPROC('12345', REFCUR);
        FETCH REFCUR BULK COLLECT INTO PERSONROLES_TABLETYPE;
        FOR indx IN 1 .. PERSONROLES_TABLETYPE.COUNT LOOP
           /* in the absence of requirements I'm just making some stuff up */
           if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL1 = 'Y' then
               personrole_rec.whatever1 := 'something';
           else
               personrole_recc.whatever1 := null;
           end if;
           if PERSONROLES_TABLETYPE(indx).IS_MANAGER_LEVEL2 = 'Y' then
               personrole_rec.whatever2 := 'something else';
           else
               personrole_recc.whatever2 := null;
           end if;
           if personrole_rec.whatever1 is not null 
           or personrole_rec.whatever2 is mot null then
                  personroles_recs.exend();
                  personroles_recs(personroles_recs.count()) := personroles_rec;
           end if;
      END LOOP;     
      CLOSE REFCUR;
      open p_cursor for 
          select * from table ( personroles_recs );
    END GET_PERSONROLES;
    

    This code uses a second collection to store the desired output. Like your code it reads the populated collection and evaluates the attributes of each row. If a value which means the criteria it sets an attribute in a rowtype variable. If one or both attributes are set it populates a new row in a second collection. At the end of the procedure it opens the ref cursor using a table() function call on the second collection.

    Note that you do not need the nested loop: you're not using the LIMIT clause so your coder reads the entire cursor into the collection in one swoop.

    The implemented rules may not be exactly what you want (because you haven't explained exactly what you want) but this should give you the general idea.

    Note that, depending on exactly what processing is masked by <DO SOME LOGIC AND ASSIGN THE VALUE TO THE ROW>, the simpler approach could still be feasible:

    open P_CURSOR for 
       select case when IS_MANAGER_LEVEL1 = 'Y' then 'YES' else 'NO' end,
              case when IS_MANAGER_LEVEL2 = 'Y' then 'YES' else 'NO' end
       from table ( PERSONROLES_TABLETYPE );