Oracle传数组必须用SQL层TABLE类型(如CREATE TYPE t AS TABLE OF VARCHAR2),VARRAY和PL/SQL关联数组(INDEX BY)不可用于跨语言调用;Java/C#须用ArrayDescriptor+ARRAY类绑定TABLE类型,遍历时应使用FIRST/NEXT或TABLE()函数,避免直接用FIRST..LAST。
Oracle里传数组必须用自定义TABLE类型,不能直接用VARRAY或内置数组
oracle没有原生的“数组”参数类型,in参数不支持varchar2(100)[]这类写法。真正能被存储过程签名接收的,只有两种:pl/sql关联数组(index by binary_integer)和sql层的table of类型。但后者才是跨语言调用(java/c#)唯一可靠的选择——因为jdbc/odp.net只认sql类型,不认pl/sql私有类型。
常见错误是创建了CREATE TYPE t_arr AS TABLE OF VARCHAR2(50),却在存储过程中写成PROCEDURE p(x IN t_arr INDEX BY BINARY_INTEGER),这会直接报编译错误:PLS-00302: component 'INDEX' must be declared。记住:SQL类型不带INDEX BY,那是PL/SQL类型才有的语法。
- SQL类型(推荐):
CREATE OR REPLACE TYPE str_list AS TABLE OF VARCHAR2(100)→ 可被JDBC/ODP.NET识别 - PL/SQL类型(仅限PL/SQL内部调用):
TYPE plsql_arr IS TABLE OF VARCHAR2(100) INDEX BY BINARY_INTEGER→ Java/C#无法绑定
Java用ARRAY类传TABLE类型,关键在ArrayDescriptor
Java端不能直接把List<String>塞给CallableStatement.setObject()。必须走Oracle JDBC专属路径:先用ArrayDescriptor.createDescriptor("STR_LIST", conn)拿到类型元信息,再用new ARRAY(descriptor, conn, values)包装。
容易踩的坑:
- 类型名大小写敏感:
"STR_LIST"必须和数据库里CREATE TYPE STR_LIST完全一致(默认大写) -
conn必须是OracleConnection,不是普通Connection;否则createDescriptor会抛ClassCastException - 空数组不能传
null,得传new String[0],否则ARRAY构造器报NullPointerException
示例片段:
ArrayDescriptor desc = ArrayDescriptor.createDescriptor("STR_LIST", oracleConn);
ARRAY array = new ARRAY(desc, oracleConn, new String[]{"a", "b", "c"});
proc.setArray(1, array);
C#用OracleCollectionType.PLSQLAssociativeArray?错,那是PL/SQL类型
很多C#示例写着Param1.CollectionType = OracleCollectionType.PLSQLAssociativeArray,但这只适用于你定义的是PL/SQL包里的TYPE t IS TABLE OF ... INDEX BY BINARY_INTEGER。如果你用的是SQL层的TABLE OF类型(比如CREATE TYPE num_list AS TABLE OF NUMBER),就必须用OracleCollectionType.Table。
混淆后果很直接:运行时报ORA-06550: wrong number or types of arguments,且错误位置指向参数1——因为驱动根本没把值绑定进去。
- SQL类型(
TABLE OF)→OracleCollectionType.Table - PL/SQL关联数组(
INDEX BY)→OracleCollectionType.PLSQLAssociativeArray - 值必须是
object[],不能是string[]混用(尤其含NULL时,要显式写new object[] { "x", null, 123 })
存储过程里遍历TABLE类型,别用FOR i IN arr.FIRST..arr.LAST
FIRST/LAST只对连续索引有效。如果前端传入的数组是稀疏的(比如Java传["a", null, "c"],底层可能生成索引1、3),arr.LAST会返回3,但arr(2)不存在,循环直接报ORA-06533: Subscript beyond count。
安全写法永远是:
- 用
arr.COUNT判断元素个数 - 用
arr.FIRST和arr.NEXT(i)迭代(最健壮) - 或者直接
FOR r IN (SELECT * FROM TABLE(arr)) LOOP(适合简单场景,但注意TABLE()函数要求类型已注册)
例如:
DECLARE
i PLS_INTEGER;
BEGIN
i := arr.FIRST;
WHILE i IS NOT NULL LOOP
INSERT INTO log_table VALUES (arr(i));
i := arr.NEXT(i);
END LOOP;
END;
真正麻烦的从来不是建类型或写存储过程,而是类型定义、驱动绑定、循环边界三者稍有不匹配,就静默失败或报错位置误导人。盯住CREATE TYPE语句、驱动使用的collection type、以及PL/SQL里遍历方式这三点,基本能避开90%的坑。


















