一、引言
在达梦数据库(DM8)的过程化SQL(PL/SQL)编程中,数组(集合类型)是批量数据处理的核心工具。它允许在存储过程中一次性传递或处理多行数据,极大提升了性能与代码的可读性。然而,在实际开发中,许多开发者(尤其是从Oracle迁移而来)常常因混淆基础类型数组 与记录类型数组的定义、初始化与传参方式,而导致编译失败或数据错误。
本文将基于达梦官方文档及实测验证的经验,系统梳理存储过程中数组类型的基本原理、定义方法、使用区别,并通过可真实运行的SQL示例,深入剖析从简单数组到复杂记录数组在存储过程入参、出参、遍历及异常处理中的最佳实践。
二、达梦数组类型的基本原理与定义
在达梦数据库中,数组(集合类型)通过 TYPE ... IS TABLE OF ... 语法定义。根据元素类型的不同,数组主要分为以下两类:
2.1 基础类型数组(标量集合)
-
定义 :数组元素为标量数据类型(如
VARCHAR2、INT、DATE)。 -
特点:存储一组简单的值,适合传递ID列表、代码列表等。
-
语法 :
sqlTYPE 数组类型名 IS TABLE OF 基础数据类型;
2.2 记录类型数组(复合集合)
-
定义 :数组元素为记录(Record) 或 对象(Object) 类型,每个元素包含多个字段(如
PARTY_ID、COMMENT、PARTY_TYPE_CODE)。 -
特点:存储结构化数据,适合批量传递业务实体或复杂查询结果集。
-
语法 (分两步):
sql-- 第一步:定义记录类型 TYPE 记录类型名 IS RECORD (字段1 类型1, 字段2 类型2, ...); -- 第二步:定义该记录的数组 TYPE 数组类型名 IS TABLE OF 记录类型名;
核心区别:基础类型数组的每个元素是一个"值",可以直接赋值;记录类型数组的每个元素是一个"结构体",必须为其中的多个字段分别赋值,或先将完整的记录对象赋给临时变量,再放入数组。
三、数组的初始化与赋值方法详解
3.1 基础类型数组的正确用法
方法一:直接构造赋值(推荐)
sql
DECLARE
TYPE T_STR_ARR IS TABLE OF VARCHAR2(50);
v_arr T_STR_ARR;
BEGIN
-- 直接使用类型名加括号和元素列表构造
v_arr := T_STR_ARR('DM', 'Oracle', 'MySQL');
FOR i IN 1..v_arr.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('第 '||i||' 个元素:'||v_arr(i));
END LOOP;
END;
/
方法二:先扩展再赋值
sql
DECLARE
TYPE T_STR_ARR IS TABLE OF VARCHAR2(50);
v_arr T_STR_ARR;
BEGIN
v_arr := T_STR_ARR(); -- 初始化为空集合
v_arr.EXTEND(3); -- 扩展3个元素空间
v_arr(1) := 'DM';
v_arr(2) := 'Oracle';
v_arr(3) := 'MySQL';
FOR i IN 1..v_arr.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('第 '||i||' 个元素:'||v_arr(i));
END LOOP;
END;
/
3.2 记录类型数组的正确用法
达梦支持以下两种赋值方式:
sql
DECLARE
-- 第一步:定义记录类型
TYPE T_PARTY IS RECORD (
PARTY_ID VARCHAR2(20),
COMMENT_TEXT VARCHAR2(100),
PARTY_TYPE_CODE VARCHAR2(10)
);
-- 第二步:定义记录数组类型
TYPE T_PARTY_ARR IS TABLE OF T_PARTY;
v_arr T_PARTY_ARR;
v_record T_PARTY; -- 临时记录变量
BEGIN
-- 正确初始化:空集合 + EXTEND
v_arr := T_PARTY_ARR();
v_arr.EXTEND(2);
-- 方式一:通过临时记录变量赋值(注意:每添加一条记录,需重新给v_record赋值)
v_record.PARTY_ID := '100020291399';
v_record.COMMENT_TEXT := '第一条记录';
v_record.PARTY_TYPE_CODE := '01';
v_arr(1) := v_record;
-- 方式二:直接给数组元素的字段赋值(更稳妥,不会覆盖丢失)
v_arr(2).PARTY_ID := '100020517340';
v_arr(2).COMMENT_TEXT := '第二条记录';
v_arr(2).PARTY_TYPE_CODE := '02';
-- 遍历输出
FOR i IN 1..v_arr.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('ID='||v_arr(i).PARTY_ID ||
', Comment='||v_arr(i).COMMENT_TEXT ||
', Type='||v_arr(i).PARTY_TYPE_CODE);
END LOOP;
END;
/
注意 :方式一在循环中如果没有及时更新
v_record,会导致后续数组元素为空,因此建议优先使用方式二(直接给元素字段赋值)。
四、全局类型与局部类型的区别
在达梦数据库中,类型的定义位置决定了它的作用域。经实测,局部类型和全局类型的使用场景差异非常明显:
4.1 局部类型(在DECLARE块内定义)
- 作用范围:仅在当前匿名块或过程体的内部可见。
- 实测结果 :基础类型数组和记录类型数组均可在匿名块内部正常声明、初始化和遍历。
- 局限性 :由于该类型未在数据库模式级别注册,无法被存储过程的参数列表引用 。如果强行将局部类型用于存储过程参数,编译器会报
-3719 非法的基类名错误。
4.2 全局类型(使用CREATE TYPE定义)
- 存储在数据库模式级别,全库共享。
- 必须使用全局类型作为存储过程的参数类型。
sql
-- 全局类型定义示例(用于跨过程传递)
CREATE OR REPLACE TYPE PARTY_CUSTRISK AS OBJECT (
PARTY_ID VARCHAR2(20),
COMMENT_TEXT VARCHAR2(100),
PARTY_TYPE_CODE VARCHAR2(10)
);
/
CREATE OR REPLACE TYPE PARTYCUSTRISK_ARRAY IS TABLE OF PARTY_CUSTRISK;
/
五、存储过程中的数组传参:IN、OUT、IN OUT
存储过程的参数模式决定了数据如何传入和传出,这是数组应用中最关键的环节。
注意 :以下示例的参数类型均使用了全局类型
PARTYCUSTRISK_ARRAY,这是保证过程成功编译的前提。
5.1 IN 参数:传入数组(只读)
IN 参数用于将数据传入过程,过程内部可以读取但不能修改传入的数组。这是批量数据输入的常用方式。
sql
-- 示例:接收记录数组作为IN参数
CREATE OR REPLACE PROCEDURE SP_PROCESS_PARTIES (
v_parties IN PARTYCUSTRISK_ARRAY
)
AS
BEGIN
FOR i IN 1..v_parties.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('处理ID:' || v_parties(i).PARTY_ID);
END LOOP;
END;
/
-- 示例:调用上述存储过程(传参)
DECLARE
v_in_arr PARTYCUSTRISK_ARRAY;
v_rec PARTY_CUSTRISK;
BEGIN
v_in_arr := PARTYCUSTRISK_ARRAY();
v_in_arr.EXTEND(2);
v_rec := NEW PARTY_CUSTRISK('100020291399', '第一条', '01');
v_in_arr(1) := v_rec;
v_rec := NEW PARTY_CUSTRISK('100020517340', '第二条', '02');
v_in_arr(2) := v_rec;
SP_PROCESS_PARTIES(v_in_arr);
END;
/
5.2 OUT 参数:返回数组(只写)
OUT 参数用于将处理结果传回调用者。在过程内部,OUT 参数可以赋值但不能读取旧值。
sql
-- 示例:接收IN参数,处理后返回OUT数组
CREATE OR REPLACE PROCEDURE SP_FILTER_PARTIES (
v_in_parties IN PARTYCUSTRISK_ARRAY,
v_out_parties OUT PARTYCUSTRISK_ARRAY
)
AS
BEGIN
v_out_parties := PARTYCUSTRISK_ARRAY();
v_out_parties.EXTEND(v_in_parties.COUNT);
FOR i IN 1..v_in_parties.COUNT LOOP
v_out_parties(i) := v_in_parties(i);
END LOOP;
END;
/
-- 示例:调用上述存储过程(声明变量接收OUT参数)
DECLARE
v_in_arr PARTYCUSTRISK_ARRAY;
v_out_arr PARTYCUSTRISK_ARRAY;
v_rec PARTY_CUSTRISK;
BEGIN
v_in_arr := PARTYCUSTRISK_ARRAY();
v_in_arr.EXTEND(2);
v_rec := NEW PARTY_CUSTRISK('100020291399', '第一条', '01');
v_in_arr(1) := v_rec;
v_rec := NEW PARTY_CUSTRISK('100020517340', '第二条', '02');
v_in_arr(2) := v_rec;
SP_FILTER_PARTIES(v_in_arr, v_out_arr);
FOR i IN 1..v_out_arr.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('返回记录'||i||':'||v_out_arr(i).PARTY_ID);
END LOOP;
END;
/
5.3 IN OUT 参数:传入并返回数组(读写)
IN OUT 参数兼顾前两者的功能。调用者传入一个初始数组,过程内部可读取并修改,修改后的结果再传回调用者。
sql
-- 示例:修改数组中的字段并回传
CREATE OR REPLACE PROCEDURE SP_UPDATE_PARTY_COMMENTS (
v_parties IN OUT PARTYCUSTRISK_ARRAY
)
AS
BEGIN
FOR i IN 1..v_parties.COUNT LOOP
v_parties(i).COMMENT_TEXT := '已处理:' || NVL(v_parties(i).COMMENT_TEXT, '无备注');
END LOOP;
END;
/
-- 示例:调用上述存储过程(传入已赋值数组,调用后打印修改结果)
DECLARE
v_in_out_arr PARTYCUSTRISK_ARRAY;
v_rec PARTY_CUSTRISK;
BEGIN
v_in_out_arr := PARTYCUSTRISK_ARRAY();
v_in_out_arr.EXTEND(2);
v_rec := NEW PARTY_CUSTRISK('100020291399', '第一条', '01');
v_in_out_arr(1) := v_rec;
v_rec := NEW PARTY_CUSTRISK('100020517340', '第二条', '02');
v_in_out_arr(2) := v_rec;
SP_UPDATE_PARTY_COMMENTS(v_in_out_arr);
FOR i IN 1..v_in_out_arr.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('修改后记录'||i||':'||v_in_out_arr(i).PARTY_ID || ', ' || v_in_out_arr(i).COMMENT_TEXT);
END LOOP;
END;
/
六、完整实战:存储过程之间的数组调用
下面模拟一个完整的场景:主过程构造数组 → 调用子过程(传入IN、接收OUT)→ 验证返回结果。
6.1 定义全局类型(用于跨过程传递)
sql
CREATE OR REPLACE TYPE PARTY_CUSTRISK AS OBJECT (
PARTY_ID VARCHAR2(20),
COMMENT_TEXT VARCHAR2(100),
PARTY_TYPE_CODE VARCHAR2(10)
);
/
CREATE OR REPLACE TYPE PARTYCUSTRISK_ARRAY IS TABLE OF PARTY_CUSTRISK;
/
6.2 创建子存储过程
sql
CREATE OR REPLACE PROCEDURE SP_SUB_PROCESS_PARTIES (
v_in_parties IN PARTYCUSTRISK_ARRAY,
v_msg_out OUT VARCHAR2,
v_out_parties OUT PARTYCUSTRISK_ARRAY
)
AS
BEGIN
v_out_parties := PARTYCUSTRISK_ARRAY();
v_out_parties.EXTEND(v_in_parties.COUNT);
FOR i IN 1..v_in_parties.COUNT LOOP
v_out_parties(i) := NEW PARTY_CUSTRISK(
v_in_parties(i).PARTY_ID,
'已处理:' || NVL(v_in_parties(i).COMMENT_TEXT, '无备注'),
v_in_parties(i).PARTY_TYPE_CODE
);
END LOOP;
v_msg_out := '处理成功,共处理 ' || v_in_parties.COUNT || ' 条数据';
EXCEPTION
WHEN OTHERS THEN
v_msg_out := '处理失败:' || SQLERRM;
END;
/
6.3 创建主存储过程(测试调用)
sql
CREATE OR REPLACE PROCEDURE SP_MAIN_TEST_ARRAY
AS
v_in_arr PARTYCUSTRISK_ARRAY;
v_out_arr PARTYCUSTRISK_ARRAY;
v_msg VARCHAR2(200);
v_rec PARTY_CUSTRISK;
BEGIN
v_in_arr := PARTYCUSTRISK_ARRAY();
v_in_arr.EXTEND(2);
v_rec := NEW PARTY_CUSTRISK('100020291399', '第一条', '01');
v_in_arr(1) := v_rec;
v_rec := NEW PARTY_CUSTRISK('100020517340', '第二条', '02');
v_in_arr(2) := v_rec;
SP_SUB_PROCESS_PARTIES(v_in_arr, v_msg, v_out_arr);
DBMS_OUTPUT.PUT_LINE('消息:' || v_msg);
FOR i IN 1..v_out_arr.COUNT LOOP
DBMS_OUTPUT.PUT_LINE('返回记录 '||i||':ID='||v_out_arr(i).PARTY_ID ||
', Comment='||v_out_arr(i).COMMENT_TEXT ||
', Type='||v_out_arr(i).PARTY_TYPE_CODE);
END LOOP;
END;
/
6.4 执行验证
sql
SET SERVEROUTPUT ON;
CALL SP_MAIN_TEST_ARRAY();
预期输出:
消息:处理成功,共处理 2 条数据
返回记录 1:ID=100020291399, Comment=已处理:第一条, Type=01
返回记录 2:ID=100020517340, Comment=已处理:第二条, Type=02
七、总结
| 比较维度 | 基础类型数组 | 记录类型数组 |
|---|---|---|
| 元素类型 | 标量(VARCHAR2等) | 记录/对象(含多个字段) |
| 初始化 | 类型名('a','b') 或 类型名() + EXTEND |
类型名() + EXTEND |
| 赋值方式 | arr(i) := '值' |
arr(i) := 记录 或 arr(i).字段 := 值 |
| 传参应用 | 传简单ID列表 | 传结构化业务数据 |
| 遍历方法 | FOR i IN 1..arr.COUNT |
FOR i IN 1..arr.COUNT(访问字段需用 arr(i).字段) |
附录:达梦官方文档参考
- DMSQL 程序数据类型与操作符
- DMSQL 程序的定义、调用与删除
- DMSQL 程序中的各种控制结构
- DMSQL 程序中的 SQL 语句
- DMSQL 程序异常处理
- 基于 C、JAVA 语法的 DMSQL 程序
- DMSQL 程序调试