0

I have an Oracle stored procedure with an array as input parameter and an array as output parameter. While the input parameter already works fine, I always get back an array of null-values (although the length of the array is what I expected).

It is only a test environment so it is a trivial example: the stored procedure only takes the input array and copy the values to the output array and to a varchar2 field, so I can see that the copy from the input array to the varchar2 field works fine but not to the output-array.

My Java Code is the following:

    DriverManager.registerDriver(new OracleDriver());
    Connection conn = DriverManager.getConnection(
            "<ConnectionString>", "<user>", "<password>");
    conn.setAutoCommit(false);
    OracleConnection oracleConnection = (OracleConnection)conn;

    OracleCallableStatement stmt = (OracleCallableStatement)oracleConnection.prepareCall("call MYPACKAGE.TABLE_IN_TABLE_OUT( ?, ?, ? )");

    String[] inputStringArray = { "1", "2", "3", "4" };
    Array inputArray = oracleConnection.createOracleArray("MYPACKAGE.CHAR_TABLE", inputStringArray);

    stmt.setArray(1, inputArray);
    stmt.registerOutParameter(2, Types.ARRAY, "MYPACKAGE.ERG_TABLE");
    stmt.registerOutParameter(3, Types.VARCHAR);
    stmt.executeUpdate();

    Array resultArray = stmt.getArray(2);
    String [] resultStringArray = (String[])resultArray.getArray();
    String resultString = stmt.getString(3);

    System.out.println(resultString);
    for (String result : resultStringArray) {
        System.out.println(result);
    }

    conn.commit();
    conn.close();

The stored procedure

create or replace PACKAGE MYPACKAGE IS 
TYPE CHAR_TABLE IS TABLE OF CHAR(01) INDEX BY BINARY_INTEGER;
TYPE ERG_TABLE IS TABLE OF CHAR(01) INDEX BY BINARY_INTEGER;


PROCEDURE TABLE_IN_TABLE_OUT(
    inputArray  IN    CHAR_TABLE,
    outputArray  OUT   ERG_TABLE,
    resultString OUT VARCHAR2
);
END MYPACKAGE;

The implementation of the stored procedure:

create or replace PACKAGE BODY MYPACKAGE AS
  PROCEDURE TABLE_IN_TABLE_OUT(
     inputArray  IN    CHAR_TABLE,
     outputArray  OUT   ERG_TABLE,
    resultString OUT VARCHAR2) AS
  BEGIN
    FOR i IN 0..inputArray.last  loop
      outputArray(i) := inputArray(i);       
    end loop;
    resultString := '';
    FOR i IN 0..outputArray.last loop
      resultString := resultString || outputArray(i);
    end loop;
  END TABLE_IN_TABLE_OUT;
END MYPACKAGE;

And this is the output:

VARCHAR2 result: 1234
Array result: null, null, null, null,  

After searching a lot on the internet and in this forum I really did not find out what I am doing wrong.

2 Answers 2

2

Don't use associative arrays defined in the PL/SQL scope (i.e. in a package) instead use a collection defined in the SQL scope.

create or replace TYPE stringlist IS TABLE OF VARCHAR2(4000);
/

create or replace TYPE stringlist2 IS TABLE OF VARCHAR2(4000);
/

Then the package is:

CREATE OR REPLACE PACKAGE mypackage
AS
  PROCEDURE table_in_table_out(
    inputArray   IN  stringlist,
    outputArray  OUT stringlist2,
    resultString OUT VARCHAR2
  );
END mypackage;
/

CREATE OR REPLACE PACKAGE BODY mypackage
AS
  PROCEDURE table_in_table_out(
    inputArray   IN  stringlist,
    outputArray  OUT stringlist2,
    resultString OUT VARCHAR2
  )
  IS
    i BINARY_INTEGER;
  BEGIN
    IF inputArray IS NULL THEN
      RETURN;
    END IF;
    outputArray := stringlist2();
    IF inputArray IS EMPTY THEN
      RETURN;
    END IF;

    -- Handle sparse arrays
    i := inputArray.FIRST;
    LOOP
      outputArray.EXTEND;
      outputArray(outputArray.LAST) := inputArray(i);
      resultString := resultString || inputArray(i);
      EXIT WHEN i = inputArray.LAST; 
      i := inputArray.NEXT(i);
    END LOOP;
  END;
END mypackage;
/

Testing in the database:

SET SERVEROUTPUT ON;

DECLARE
  i stringList := StringList( 'A', 'C', 'F' );
  e stringlist2;
  s VARCHAR2(4000);
  n BINARY_INTEGER;
BEGIN
  i.DELETE(2);
  n := i.FIRST;
  LOOP
    DBMS_OUTPUT.PUT_LINE( n || ': ' || i(n) );
    EXIT WHEN n = i.LAST;
    n := i.NEXT(n);
  END LOOP;
  mypackage.table_in_table_out( i, e, s );
  DBMS_OUTPUT.PUT_LINE( s );
  n := e.FIRST;
  LOOP
    DBMS_OUTPUT.PUT_LINE( n || ': ' || e(n) );
    EXIT WHEN n = e.LAST;
    n := e.NEXT(n);
  END LOOP;
END;
/

Java:

try{
  Class.forName( "oracle.jdbc.OracleDriver" );

  Connection con = DriverManager.getConnection(
      "jdbc:oracle:thin:@localhost:1521:orcl",
      "username",
      "password"
  );

  OracleConnection oCon = (OracleConnection) con;

  OracleCallableStatement st = (OracleCallableStatement) con.prepareCall( "{ call mypackage.table_in_table_out( :chars, :ergs, :res )}" );

  ARRAY ia = oCon.createARRAY("STRINGLIST", new String[]{ "A", "C", "F"} );
  st.setARRAYAtName("chars", ia );
  st.registerOutParameter( 2, java.sql.Types.ARRAY, "VARCHAR2S_TABLE" );
  st.registerOutParameter( 3, java.sql.Types.VARCHAR );

  System.out.println( st.execute() );
  System.out.println( st.getString( 3 ) );
  String[] strs = (String[]) st.getARRAY(2).getArray();

  for ( String str : strs )
    System.out.println(str);

  st.close();
  con.close();
} catch (ClassNotFoundException | SQLException ex) {
  System.out.println( ex.getMessage() );
  ex.printStackTrace();
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks. I tried this and it works. But do you have experience with the "registerIndexTableOutParameter" and "getOraclePlsqlIndexTable"? It looks to me as this would be a possibility to also acces associative arrays. Am I wrong?
1

Finally (after hours of researching) I found also the way to return index-by Tables which works now. Since it was a very painful way I want to share my solution here:

    DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());
    Connection conn = DriverManager.getConnection(
            "<connectionString>", "<user>", "<password>");
    conn.setAutoCommit(false);
    OracleConnection oracleConnection = (OracleConnection)conn;
    OracleCallableStatement stmt = (OracleCallableStatement)oracleConnection.prepareCall("BEGIN MYPACKAGE.TABLE_IN_TABLE_OUT( ?, ?, ? ); END;");

    String[] inputStringArray = { "1", "2", "3", "4", "5", "6"};
    Array inputArray = oracleConnection.createOracleArray("MYPACKAGE.CHAR_TABLE", inputStringArray);

    stmt.setArray(1, inputArray);
    stmt.registerIndexTableOutParameter(2, 100, OracleTypes.VARCHAR, 100);
    stmt.registerOutParameter(3, Types.VARCHAR);
    stmt.execute();

    String resultString = stmt.getString(3);
    String[] resultArray = (String[])stmt.getPlsqlIndexTable(2);

    System.out.println("VARCHAR2 result: " + resultString);
    System.out.print("Array result: ");
    for (String result : resultArray) {
        System.out.print(result + ", ");
    }

The most important things that I have changed:

  1. I changed the Call String from:

    oracleConnection.prepareCall("call MYPACKAGE.TABLE_IN_TABLE_OUT( ?, ?, ? )");
    

    to

    oracleConnection.prepareCall("BEGIN MYPACKAGE.TABLE_IN_TABLE_OUT( ?, ?, ? ); END;");
    

    because I was running into an "ORA-01484: array can only be bound to PL/SQL statements" when using the Method "registerIndexTableOutParameter" (see next point).

  2. Instead of registering the Array this way:

      stmt.registerOutParameter(2, Types.ARRAY, "MYPACKAGE.ERG_TABLE");
    

    i do it this way now:

     stmt.registerIndexTableOutParameter(2, 100, OracleTypes.VARCHAR, 100);
    
  3. To get the array I had to use the following code:

    String[] resultArray = (String[])stmt.getPlsqlIndexTable(2);
    

And thats all. Hopefully this helps others.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.