注册
DMSQL存储过程开发
专栏/技术分享/ 文章详情 /

DMSQL存储过程开发

annie 2025/12/05 12 0 0
摘要

在应用开发中,将业务逻辑下沉至数据库服务器端执行已成为提升性能、保障数据一致性和降低网络开销的重要手段。达梦数据库(DM Database)为此提供了强大的 DMSQL(Dameng Structured Query Language) 扩展语言。              DMSQL 不仅完全兼容标准 SQL,更在此基础上融合了过程化编程的能力,使其  成为一种功能完备的服务器端存储过程语言。本文将带您系统性地学习 DMSQL 的核心开发技术,助您构建高效、健壮的数据库应用。
一、DMSQL基础:块结构与基本范式
DMSQL 程序的基本单元是 “块”(Block)。一个标准的 DMSQL 块由三个部分组成,其结构清晰,职责分明:
DECLARE
    /* 声明部分: 在此声明 DMSQL 用到的变量, 类型, 游标, 以及局部的存储过程和函数 /
BEGIN
    /
执行部分: 过程及 SQL 语句, 即程序的主要部分 /
EXCEPTION
    /
执行异常部分: 错误处理 */
END;
·   DECLARE 部分:这是可选的,用于定义程序运行所需的所有元素,如变量、常量、自定义类型、游标等。
·   BEGIN…END 部分:这是必须的,包含了程序的核心逻辑,即一系列的 SQL 语句和过程化控制语句。
·   EXCEPTION 部分:这也是可选的,专门用于捕获和处理程序执行过程中可能出现的错误。
最佳实践提示:为避免潜在的命名冲突和逻辑错误,强烈建议不要将变量名声明为与数据库表中的字段名完全相同。
1.1 变量命名规范
良好的命名规范是编写可读、可维护代码的第一步。达梦推荐以下命名约定:
image.png
遵循统一的命名规范,能让代码意图一目了然。
二、DMSQL 中的变量与数据类型
变量是程序存储和操作数据的基础。在 DMSQL 中,所有变量都必须在 DECLARE 部分进行声明。
2.1 变量声明语法
variable_name [ CONSTANT ] type [ NOT NULL ] [ := value ];
·   variable_name: 变量名。
·   CONSTANT: (可选)声明为常量,一旦赋值不可更改。
·   type: 数据类型,可以是标量类型(如 NUMBER, VARCHAR)或复合类型。
·   NOT NULL: (可选)声明变量不能为空,此时必须提供初始值。
·   := value: 赋予变量初始值。
示例:
DECLARE
    V_Description VARCHAR(50);           – 声明一个可为空的字符串变量
    V_Number      NUMBER := 45;          – 声明并初始化一个数值变量
    C_PI          CONSTANT NUMBER := 3.14159; – 声明一个常量
    V_MustHaveVal VARCHAR(20) NOT NULL := ‘Initialized’; – 非空变量必须初始化
BEGIN
    – 程序主体
END;
2.2 复合数据类型:超越标量
DMSQL 提供了多种复合数据类型,用于处理更复杂的业务场景。

  1. 记录类型 (RECORD)记录类型允许将逻辑上相关的多个字段组合成一个单一的变量,类似于其他语言中的结构体(Struct)。
    – 定义记录类型
    TYPE t_EmpRecord IS RECORD (
        T_no   employee.employee_id%TYPE,
        T_name employee.employee_name%TYPE,
        T_sal  employee.salary%TYPE
    );
    – 声明记录变量
    v_emp t_EmpRecord;
  2. %TYPE 属性%TYPE 用于声明一个变量,使其数据类型与数据库表中的某个列或另一个已声明变量的类型完全一致。这极大地提高了代码的健壮性和可维护性,当表结构变更时,相关变量会自动适应。
    DECLARE
        v_empno employee.employee_id%TYPE := 1001; – 类型与 employee_id 列相同
    BEGIN
        – …
    END;
  3. %ROWTYPE 属性%ROWTYPE 是 %TYPE 的强大扩展,它能声明一个记录变量,其结构与整个数据库表或视图的结构完全一致。
    DECLARE
        rec employee%ROWTYPE; – rec 包含 employee 表的所有字段
    BEGIN
        SELECT * INTO rec FROM employee WHERE employee_id = 1001;
        PRINT(‘姓名:’ || rec.employee_name || ’ 工资:’ || rec.salary);
    END;
  4. DMSQL 表 (索引表/Index-by Table)这是一种内存中的、类似数组的结构,但其下标可以是任意整数(包括负数),非常灵活。
    DECLARE
        TYPE dept_table_type IS TABLE OF department%ROWTYPE INDEX BY BINARY_INTEGER;
        my_dept_table dept_table_type;
        v_index BINARY_INTEGER := -1;
    BEGIN
        my_dept_table(v_index).department_name := ‘开发部’;
        PRINT(my_dept_table(v_index).department_name); – 输出: 开发部
    END;
  5. 可变数组 (VARRAY)与索引表不同,VARRAY 在声明时必须指定最大容量,其元素数量在0到最大容量之间动态变化。
    DECLARE
        TYPE strings IS VARRAY(5) OF VARCHAR(10);
        v_list strings := strings(‘scott’,‘peter’,‘smith’,‘tom’); – 初始化4个元素
    BEGIN
        v_list.EXTEND; – 扩容一个元素
        v_list(5) := ‘dameng’;
        PRINT(v_list(5)); – 输出: dameng
    END;
    三、流程控制:赋予程序逻辑灵魂
    DMSQL 提供了丰富的流程控制语句,使程序能够根据条件做出判断或重复执行任务。
    3.1 条件分支:IF 与 CASE
    IF 语句 是最常用的条件判断结构。
    DECLARE
        v_salary emp.salary%TYPE;
        v_comment VARCHAR(35);
    BEGIN
        SELECT salary INTO v_salary FROM emp WHERE employee_id = &employee_id;
       
        IF v_salary < 1500 THEN
            v_comment := ‘Fairly less’;
        ELSIF v_salary < 3000 THEN
            v_comment := ‘A little more’;
        ELSE
            v_comment := ‘Lots of salary’;
        END IF;
       
        PRINT(v_comment);
    END;
    CASE 语句 适用于基于单一选择器的多路分支,代码更简洁。
    DECLARE
        v_grade CHAR(1) := UPPER(’&p_grade’);
        v_appraisal VARCHAR(20);
    BEGIN
        v_appraisal := CASE v_grade
            WHEN ‘A’ THEN ‘Excellent’
            WHEN ‘B’ THEN ‘Very Good’
            WHEN ‘C’ THEN ‘Good’
            ELSE ‘No such grade’
        END;
        PRINT('Grade: ’ || v_grade || ’ Appraisal: ’ || v_appraisal);
    END;
    3.2 循环结构:LOOP, WHILE, FOR
    简单 LOOP 需要显式使用 EXIT WHEN 来退出,否则会陷入无限循环。
    DECLARE
        v1 INT := 0;
    BEGIN
        LOOP
            v1 := v1 + 1;
            PRINT('当前值为: ’ || v1);
            EXIT WHEN v1 = 10; – 关键退出条件
        END LOOP;
    END;
    WHILE 循环 在每次迭代前检查条件。
    DECLARE
        x INT := 1;
    BEGIN
        WHILE x <= 10 LOOP
            PRINT('X 的当前值为: ’ || x);
            x := x + 1;
        END LOOP;
    END;
    FOR 循环 最为简洁,自动管理循环计数器。
    BEGIN
        FOR v_counter IN 1 … 5 LOOP – 正向循环
            PRINT('Counter: ’ || v_counter);
        END LOOP;
       
        FOR v_counter IN REVERSE 1 … 5 LOOP – 反向循环
            PRINT('Reverse Counter: ’ || v_counter);
        END LOOP;
    END;
    四、游标:处理多行数据的利器
    当 SQL 查询返回多行结果时,就需要使用 游标(Cursor) 来逐行处理数据。
    4.1 显式游标
    显式游标需要程序员手动完成“声明-打开-取值-关闭”四个步骤。
    DECLARE
        v_ename employee.employee_name%TYPE;
        v_sal   employee.salary%TYPE;
        – 1. 声明游标
        CURSOR c_cursor IS
            SELECT employee_name, salary FROM employee WHERE ROWNUM < 11;
    BEGIN
        – 2. 打开游标
        OPEN c_cursor;
        – 3. 提取第一行数据
        FETCH c_cursor INTO v_ename, v_sal;
        – 4. 循环处理
        WHILE c_cursor%FOUND LOOP
            PRINT(v_ename || '的工资是: ’ || TO_CHAR(v_sal));
            FETCH c_cursor INTO v_ename, v_sal; – 提取下一行
        END LOOP;
        – 5. 关闭游标
        CLOSE c_cursor;
    END;
    游标属性 是控制循环的关键:
    ·   %FOUND: 最近一次 FETCH 操作成功获取了数据。
    ·   %NOTFOUND: 最近一次 FETCH 操作未获取到数据(已到末尾)。
    ·   %ISOPEN: 游标是否处于打开状态。
    ·   %ROWCOUNT: 自游标打开以来,已成功获取的行数。
    参数化游标 允许在打开游标时传入参数,增加灵活性。
    CURSOR c_cursor(P_sal employee.salary%TYPE) IS
        SELECT employee_name, salary FROM employee WHERE salary >= P_sal;
    – 使用: OPEN c_cursor(20000);
    4.2 游标 FOR 循环
    这是处理游标的最优雅方式,DMSQL 会自动处理打开、取值、关闭等繁琐操作。
    DECLARE
        CURSOR c_sal IS
            SELECT employee_id, employee_name, salary FROM employee;
    BEGIN
        FOR v_sal IN c_sal LOOP
            – v_sal 是一个隐式的 %ROWTYPE 记录变量
            PRINT(v_sal.employee_id || ‘—’ || v_sal.employee_name || ‘—’ || v_sal.salary);
        END LOOP;
        – 游标在此处自动关闭
    END;
    4.3 隐式游标
    对于 INSERT, UPDATE, DELETE 等非查询语句,达梦会自动创建一个名为 SQL 的隐式游标。我们可以通过其属性来获取操作结果。
    BEGIN
        DELETE FROM employee WHERE department_id = 101;
        IF SQL%NOTFOUND THEN
            DELETE FROM department WHERE department_id = 101;
            PRINT(‘部门已被删除,因其无员工。’);
        END IF;
    END;
    五、异常处理:构建健壮程序的最后防线
    完善的异常处理机制是专业级代码的标志。DMSQL 的 EXCEPTION 块可以捕获并处理运行时错误。
    5.1 预定义异常
    达梦内置了许多常见的异常,如 NO_DATA_FOUND(SELECT INTO 未找到数据)、TOO_MANY_ROWS(返回多行)等,可直接使用。
    DECLARE
        v_sal employee.salary%TYPE;
    BEGIN
        SELECT salary INTO v_sal FROM employee WHERE employee_id = 99999;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            PRINT(‘数据库中没有该员工!’);
        WHEN TOO_MANY_ROWS THEN
            PRINT(‘员工ID不唯一,数据异常!’);
    END;
    5.2 用户自定义异常
    对于业务逻辑特有的错误,可以定义自己的异常。
    DECLARE
        v_empno employee.employee_id%TYPE := &empno;
        no_result EXCEPTION; – 1. 声明自定义异常
    BEGIN
        UPDATE employee SET salary = salary + 100 WHERE employee_id = v_empno;
        IF SQL%NOTFOUND THEN
            RAISE no_result; – 2. 显式抛出异常
        END IF;
    EXCEPTION
        WHEN no_result THEN – 3. 捕获并处理
            PRINT(‘你的数据更新语句失败了!’);
        WHEN OTHERS THEN
            NULL; – 捕获所有其他未处理的异常
    END;
    通过 RAISE 语句,我们可以主动触发异常,将控制权转移至 EXCEPTION 块。
    六、总结
    DMSQL 作为达梦数据库强大的过程化扩展语言,为开发者提供了从变量管理、流程控制到复杂数据处理(游标)和错误恢复(异常处理)的一整套解决方案。掌握 DMSQL 不仅能显著提升数据库应用的性能和安全性,还能将复杂的业务规则内置于数据层,实现更高层次的架构解耦。本文所涵盖的知识点构成了 DMSQL 开发的核心骨架,希望这份指南能为您后续的深入学习和项目实践奠定坚实的基础。

https://eco.dameng.com

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服