达梦数据库存储过程中的数组类型详解:基础数组与记录数组的差异化应用

一、引言

在达梦数据库(DM8)的过程化SQL(PL/SQL)编程中,数组(集合类型)是批量数据处理的核心工具。它允许在存储过程中一次性传递或处理多行数据,极大提升了性能与代码的可读性。然而,在实际开发中,许多开发者(尤其是从Oracle迁移而来)常常因混淆基础类型数组记录类型数组的定义、初始化与传参方式,而导致编译失败或数据错误。

本文将基于达梦官方文档及实测验证的经验,系统梳理存储过程中数组类型的基本原理、定义方法、使用区别,并通过可真实运行的SQL示例,深入剖析从简单数组到复杂记录数组在存储过程入参、出参、遍历及异常处理中的最佳实践。


二、达梦数组类型的基本原理与定义

在达梦数据库中,数组(集合类型)通过 TYPE ... IS TABLE OF ... 语法定义。根据元素类型的不同,数组主要分为以下两类:

2.1 基础类型数组(标量集合)

  • 定义 :数组元素为标量数据类型(如 VARCHAR2INTDATE)。

  • 特点:存储一组简单的值,适合传递ID列表、代码列表等。

  • 语法

    sql 复制代码
    TYPE 数组类型名 IS TABLE OF 基础数据类型;

2.2 记录类型数组(复合集合)

  • 定义 :数组元素为记录(Record)对象(Object) 类型,每个元素包含多个字段(如 PARTY_IDCOMMENTPARTY_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).字段

附录:达梦官方文档参考


相关推荐
梦Arrebol2 小时前
Redis 内容及相关实验
数据库·redis
大模型码小白2 小时前
Spring AI Tool 实现自然语言操作 MySQL 数据库详解
服务器·开发语言·数据库·人工智能·python·mysql·spring
DBA大董4 小时前
TDengine3.0 DBA常用的运维命令和SQL2
运维·数据库·时序数据库·dba·tdengine
醉颜凉4 小时前
DM 数据库管理员的任务:保障系统高可用与数据安全的实践指南
达梦数据库·dm数据库·数据库管理员·数据库运维·dba任务
ManageEngineITSM4 小时前
什么是CMDB?配置管理数据库的定义、作用与建设方法一文讲清
大数据·数据库·人工智能·资产管理·变更管理
IvorySQL4 小时前
PostgreSQL 日报|PG19 外键快速路径隐患已修复(8 月 23 日)
数据库·postgresql
Elastic 中国社区官方博客5 小时前
OpenTelemetry Java 扩展:无需分叉 agent 即可自定义追踪
java·大数据·运维·开发语言·数据库·人工智能·elasticsearch
01传说5 小时前
redis开机自启脚本
数据库·redis·缓存