Search code examples
oracleplsqloracle10gplsql-package

I tried to fetch bulkcollect records from sys_refcursor and tried to insert another table using forall but it throw an error message


--My first procedure

CREATE OR REPLACE PROCEDURE P1(V_SALARY NUMBER,OUTPUT_VALUE OUT SYS_REFCURSOR)
AS
BEGIN
  OPEN OUTPUT_VALUE FOR 
  SELECT FIRST_NAME,LAST_NAME
    FROM EMPLOYEES
   WHERE SALARY >V_SALARY;
END;

--Second procedure

CREATE OR REPLACE PROCEDURE P2(V_SAL NUMBER)
AS
RETURN_VALUE SYS_REFCURSOR;
  TYPE TTT IS RECORD(FIRST_NAME VARCHAR2(30),LAST_NAME VARCHAR2(20)) ;
  I TTT;
  TYPE TNAME IS TABLE OF TTT INDEX BY BINARY_INTEGER;
  K TNAME;
BEGIN
  P1(V_SAL,RETURN_VALUE);
  FETCH RETURN_VALUE BULK COLLECT INTO K;
  FORALL X IN K.FIRST..K.LAST 
    INSERT INTO T1 VALUES (K(X).FIRST_NAME,K(X).LAST_NAME);
  CLOSE RETURN_VALUE;
END;
/

ERROR at line 12: PLS-00436: implementation restriction: cannot reference fields of BULK In-BIND table of records

but if i tried to insert for loop then it is working fine..It is not working when i use Forall(bulk bind)


Solution

  • This looks like some bug or restriction when you try to do bulk binding while returning collection or sys_refcursor from another Procedure. However, you have many other options rather bulk binding (Looping using FORALL) which are equally performant.

    Alternative are as below:

    Table Preparation:

    Create table employees (
      first_name   varchar2(100),
      last_name    varchar2(100),
      salary       number
    );
    
    Insert into employees values('A','B',1000);
    Insert into employees values('C','D',2000);
    Insert into employees values('E','F',3000);
    
    
    Create table t1 (
      first_name   varchar2(100),
      last_name    varchar2(100)  
    );
    
    --Using Objects in place of records and sys_refcursor.
    Create or replace type return_value is Object (
      first_name   varchar2(100),
      last_name    varchar2(100)  
    );
    
    Create or replace  type v_ret_val is table of return_value;
    

    Option 1: Declaring P1 as Function and then using collection as direct path insert.

    CREATE OR REPLACE function p1 (v_salary NUMBER)      
     return v_ret_val
      AS
      abc v_ret_val;
    BEGIN
      SELECT return_value(first_name,last_name) 
      Bulk collect into
        abc
      FROM employees
      WHERE salary > v_salary;
    
     Return abc;
    END;
    ---------------    
    CREATE OR REPLACE PROCEDURE p2 (
          v_sal NUMBER
        ) AS
          k   v_ret_val;
        BEGIN     
    
          k:=p1(v_sal);
    
          --Displaying values of collection
          For i in 1..k.count
          Loop
            dbms_output.put_line(k(i).first_name || k(i).last_name);
          End Loop;
    
          --Direct path insert
          INSERT /*+Append*/ INTO t1
          Select t.first_name,t.last_name
          from (table( k )) t;
          COMMIT;
        END;
    

    Option 2: Declaring P1 as procedure with OUT parameter and then using collection as direct path insert.

    CREATE OR REPLACE procedure p11 (
      v_salary NUMBER
      ,output_value OUT v_ret_val
    ) 
    AS  
    BEGIN
      SELECT return_value(first_name,last_name) 
      Bulk collect into
        output_value
      FROM employees
      WHERE salary > v_salary;
    
    END;
    ------------------------------
    
    CREATE OR REPLACE PROCEDURE p2 ( v_sal NUMBER) 
    AS
      k   v_ret_val;
    BEGIN
      p11(v_sal,k);     
    
      For i in 1..k.count
      Loop
        dbms_output.put_line(k(i).first_name || k(i).last_name);
      End Loop;
    
      INSERT /*+Append*/ INTO t1
      Select t.first_name,t.last_name
      from (table( k )) t;
    
    END;
    

    Option 3: Declaring P1 as procedure with OUT parameter and then Merging the collection with target Table.

    CREATE OR REPLACE PROCEDURE p2 (
      v_sal NUMBER
    ) AS
      k   v_ret_val;
    BEGIN
      p11(v_sal,k);     
    
      For i in 1..k.count
      Loop
        dbms_output.put_line(k(i).first_name || k(i).last_name);
      End Loop;  
    
    MERGE INTO t1 tgt 
      Using ( Select t.first_name,
                                       t.last_name
                                from table ( k ) t
                              ) src
      on (
        tgt.first_name = src.first_name 
        and tgt.last_name = src.first_name
        )
      When not matched 
      then 
       insert (
               tgt.first_name,
               tgt.last_name
             ) 
       values (
                src.first_name,
                src.last_name
              );     
    END;
    

    Execution:

     Exec P2(1000);
    

    Output:

    SQL> Select * from t1;
    
        FIRST_NAME                         LAST_NAME
        -------------------------------------------
        E                                     F
        C                                     D